aboutsummaryrefslogtreecommitdiff
path: root/src/ThemeBuilder
diff options
context:
space:
mode:
Diffstat (limited to 'src/ThemeBuilder')
-rw-r--r--src/ThemeBuilder/CMakeLists.txt45
-rw-r--r--src/ThemeBuilder/components/Common.cpp22
-rw-r--r--src/ThemeBuilder/components/Common.h7
-rw-r--r--src/ThemeBuilder/components/Editor.cpp23
-rw-r--r--src/ThemeBuilder/components/Editor.h25
-rw-r--r--src/ThemeBuilder/components/HeadBodyEditor.cpp30
-rw-r--r--src/ThemeBuilder/components/HeadBodyEditor.h36
-rw-r--r--src/ThemeBuilder/components/MainWindow.cpp37
-rw-r--r--src/ThemeBuilder/components/MainWindow.h32
-rw-r--r--src/ThemeBuilder/components/StyleRuleEditor.cpp62
-rw-r--r--src/ThemeBuilder/components/StyleRuleEditor.h45
-rw-r--r--src/ThemeBuilder/components/StyleRuleSetEditor.cpp123
-rw-r--r--src/ThemeBuilder/components/StyleRuleSetEditor.h44
-rw-r--r--src/ThemeBuilder/components/conditions/CheckedConditionEditor.cpp26
-rw-r--r--src/ThemeBuilder/components/conditions/CheckedConditionEditor.h28
-rw-r--r--src/ThemeBuilder/components/conditions/ClickStateConditionEditor.cpp69
-rw-r--r--src/ThemeBuilder/components/conditions/ClickStateConditionEditor.h30
-rw-r--r--src/ThemeBuilder/components/conditions/CompoundConditionEditor.cpp109
-rw-r--r--src/ThemeBuilder/components/conditions/CompoundConditionEditor.h72
-rw-r--r--src/ThemeBuilder/components/conditions/ConditionEditor.cpp50
-rw-r--r--src/ThemeBuilder/components/conditions/ConditionEditor.h17
-rw-r--r--src/ThemeBuilder/components/conditions/FocusConditionEditor.cpp25
-rw-r--r--src/ThemeBuilder/components/conditions/FocusConditionEditor.h28
-rw-r--r--src/ThemeBuilder/components/conditions/NoConditionEditor.cpp7
-rw-r--r--src/ThemeBuilder/components/conditions/NoConditionEditor.h17
-rw-r--r--src/ThemeBuilder/components/properties/CheckBoxPropertyEditor.cpp19
-rw-r--r--src/ThemeBuilder/components/properties/CheckBoxPropertyEditor.h29
-rw-r--r--src/ThemeBuilder/components/properties/ColorPropertyEditor.cpp48
-rw-r--r--src/ThemeBuilder/components/properties/ColorPropertyEditor.h36
-rw-r--r--src/ThemeBuilder/components/properties/CornerRadiusPropertyEditor.cpp42
-rw-r--r--src/ThemeBuilder/components/properties/CornerRadiusPropertyEditor.h28
-rw-r--r--src/ThemeBuilder/components/properties/FontPropertyEditor.cpp60
-rw-r--r--src/ThemeBuilder/components/properties/FontPropertyEditor.h38
-rw-r--r--src/ThemeBuilder/components/properties/MeasureLengthPropertyEditor.cpp37
-rw-r--r--src/ThemeBuilder/components/properties/MeasureLengthPropertyEditor.h35
-rw-r--r--src/ThemeBuilder/components/properties/OptionalPropertyEditor.h65
-rw-r--r--src/ThemeBuilder/components/properties/PointPropertyEditor.cpp38
-rw-r--r--src/ThemeBuilder/components/properties/PointPropertyEditor.h35
-rw-r--r--src/ThemeBuilder/components/properties/SelectPropertyEditor.cpp15
-rw-r--r--src/ThemeBuilder/components/properties/SelectPropertyEditor.h42
-rw-r--r--src/ThemeBuilder/components/properties/TextPropertyEditor.cpp22
-rw-r--r--src/ThemeBuilder/components/properties/TextPropertyEditor.h31
-rw-r--r--src/ThemeBuilder/components/properties/ThicknessPropertyEditor.cpp34
-rw-r--r--src/ThemeBuilder/components/properties/ThicknessPropertyEditor.h31
-rw-r--r--src/ThemeBuilder/components/stylers/BorderStylerEditor.cpp100
-rw-r--r--src/ThemeBuilder/components/stylers/BorderStylerEditor.h36
-rw-r--r--src/ThemeBuilder/components/stylers/CompoundStylerEditor.cpp105
-rw-r--r--src/ThemeBuilder/components/stylers/CompoundStylerEditor.h30
-rw-r--r--src/ThemeBuilder/components/stylers/ContentBrushStylerEditor.cpp31
-rw-r--r--src/ThemeBuilder/components/stylers/ContentBrushStylerEditor.h24
-rw-r--r--src/ThemeBuilder/components/stylers/CursorStylerEditor.cpp65
-rw-r--r--src/ThemeBuilder/components/stylers/CursorStylerEditor.h24
-rw-r--r--src/ThemeBuilder/components/stylers/FontStylerEditor.cpp23
-rw-r--r--src/ThemeBuilder/components/stylers/FontStylerEditor.h23
-rw-r--r--src/ThemeBuilder/components/stylers/MarginStylerEditor.cpp28
-rw-r--r--src/ThemeBuilder/components/stylers/MarginStylerEditor.h25
-rw-r--r--src/ThemeBuilder/components/stylers/PaddingStylerEditor.cpp28
-rw-r--r--src/ThemeBuilder/components/stylers/PaddingStylerEditor.h25
-rw-r--r--src/ThemeBuilder/components/stylers/PreferredSizeStylerEditor.cpp34
-rw-r--r--src/ThemeBuilder/components/stylers/PreferredSizeStylerEditor.h27
-rw-r--r--src/ThemeBuilder/components/stylers/StylerEditor.cpp63
-rw-r--r--src/ThemeBuilder/components/stylers/StylerEditor.h16
-rw-r--r--src/ThemeBuilder/main.cpp25
63 files changed, 2426 insertions, 0 deletions
diff --git a/src/ThemeBuilder/CMakeLists.txt b/src/ThemeBuilder/CMakeLists.txt
new file mode 100644
index 00000000..93b24e85
--- /dev/null
+++ b/src/ThemeBuilder/CMakeLists.txt
@@ -0,0 +1,45 @@
+add_executable(CruThemeBuilder
+ main.cpp
+ components/Common.cpp
+ components/Editor.cpp
+ components/HeadBodyEditor.cpp
+ components/MainWindow.cpp
+ components/StyleRuleEditor.cpp
+ components/StyleRuleSetEditor.cpp
+ components/conditions/CheckedConditionEditor.cpp
+ components/conditions/ClickStateConditionEditor.cpp
+ components/conditions/CompoundConditionEditor.cpp
+ components/conditions/ConditionEditor.cpp
+ components/conditions/FocusConditionEditor.cpp
+ components/conditions/NoConditionEditor.cpp
+ components/properties/CheckBoxPropertyEditor.cpp
+ components/properties/ColorPropertyEditor.cpp
+ components/properties/CornerRadiusPropertyEditor.cpp
+ components/properties/FontPropertyEditor.cpp
+ components/properties/MeasureLengthPropertyEditor.cpp
+ components/properties/PointPropertyEditor.cpp
+ components/properties/SelectPropertyEditor.cpp
+ components/properties/TextPropertyEditor.cpp
+ components/properties/ThicknessPropertyEditor.cpp
+ components/stylers/BorderStylerEditor.cpp
+ components/stylers/CompoundStylerEditor.cpp
+ components/stylers/ContentBrushStylerEditor.cpp
+ components/stylers/CursorStylerEditor.cpp
+ components/stylers/FontStylerEditor.cpp
+ components/stylers/MarginStylerEditor.cpp
+ components/stylers/PaddingStylerEditor.cpp
+ components/stylers/PreferredSizeStylerEditor.cpp
+ components/stylers/StylerEditor.cpp
+)
+
+if(APPLE)
+ set_target_properties(CruThemeBuilder PROPERTIES
+ MACOSX_BUNDLE TRUE
+ MACOSX_BUNDLE_BUNDLE_NAME cru-theme-builder
+ MACOSX_BUNDLE_GUI_IDENTIFIER life.crupest.cru.theme-builder
+ )
+endif()
+
+target_add_resources(CruThemeBuilder cru/ui)
+target_add_resources(CruThemeBuilder cru/theme_builder)
+target_link_libraries(CruThemeBuilder PRIVATE CruPlatformBootstrap CruUi)
diff --git a/src/ThemeBuilder/components/Common.cpp b/src/ThemeBuilder/components/Common.cpp
new file mode 100644
index 00000000..75d5deb0
--- /dev/null
+++ b/src/ThemeBuilder/components/Common.cpp
@@ -0,0 +1,22 @@
+#include "Common.h"
+#include "cru/platform/Color.h"
+#include "cru/platform/graphics/Factory.h"
+#include "cru/platform/gui/UiApplication.h"
+
+#include <random>
+
+namespace cru::theme_builder::components {
+std::unique_ptr<platform::graphics::ISolidColorBrush>
+CreateRandomEditorBackgroundBrush() {
+ static float current_hue = 0.0f;
+ current_hue += 23.f;
+ if (current_hue > 360.f) {
+ current_hue -= 360.f;
+ }
+
+ return platform::gui::IUiApplication::GetInstance()
+ ->GetGraphicsFactory()
+ ->CreateSolidColorBrush(platform::HslColor(current_hue, 0.5f, 0.8f));
+}
+
+} // namespace cru::theme_builder::components
diff --git a/src/ThemeBuilder/components/Common.h b/src/ThemeBuilder/components/Common.h
new file mode 100644
index 00000000..b91fcf88
--- /dev/null
+++ b/src/ThemeBuilder/components/Common.h
@@ -0,0 +1,7 @@
+#pragma once
+#include "cru/platform/graphics/Brush.h"
+
+namespace cru::theme_builder::components {
+std::unique_ptr<platform::graphics::ISolidColorBrush>
+CreateRandomEditorBackgroundBrush();
+}
diff --git a/src/ThemeBuilder/components/Editor.cpp b/src/ThemeBuilder/components/Editor.cpp
new file mode 100644
index 00000000..89e27e83
--- /dev/null
+++ b/src/ThemeBuilder/components/Editor.cpp
@@ -0,0 +1,23 @@
+#include "Editor.h"
+
+namespace cru::theme_builder::components {
+
+void Editor::RaiseChangeEvent() {
+ if (suppress_next_change_event_) {
+ suppress_next_change_event_ = false;
+ } else {
+ change_event_.Raise(nullptr);
+ }
+}
+
+void Editor::SuppressNextChangeEvent() { suppress_next_change_event_ = true; }
+
+void Editor::ConnectChangeEvent(IEvent<std::nullptr_t>* event) {
+ event->AddHandler([this](std::nullptr_t) { RaiseChangeEvent(); });
+}
+
+void Editor::ConnectChangeEvent(Editor* editor) {
+ ConnectChangeEvent(editor->ChangeEvent());
+}
+
+} // namespace cru::theme_builder::components
diff --git a/src/ThemeBuilder/components/Editor.h b/src/ThemeBuilder/components/Editor.h
new file mode 100644
index 00000000..29809c82
--- /dev/null
+++ b/src/ThemeBuilder/components/Editor.h
@@ -0,0 +1,25 @@
+#pragma once
+#include "cru/common/Event.h"
+#include "cru/ui/components/Component.h"
+
+namespace cru::theme_builder::components {
+class Editor : public ui::components::Component {
+ public:
+ Editor() = default;
+ ~Editor() override = default;
+
+ public:
+ IEvent<std::nullptr_t>* ChangeEvent() { return &change_event_; }
+
+ protected:
+ void RaiseChangeEvent();
+ void SuppressNextChangeEvent();
+ void ConnectChangeEvent(IEvent<std::nullptr_t>* event);
+ void ConnectChangeEvent(Editor* editor);
+ void ConnectChangeEvent(Editor& editor) { ConnectChangeEvent(&editor); }
+
+ private:
+ bool suppress_next_change_event_ = false;
+ Event<std::nullptr_t> change_event_;
+};
+} // namespace cru::theme_builder::components
diff --git a/src/ThemeBuilder/components/HeadBodyEditor.cpp b/src/ThemeBuilder/components/HeadBodyEditor.cpp
new file mode 100644
index 00000000..6d7ace66
--- /dev/null
+++ b/src/ThemeBuilder/components/HeadBodyEditor.cpp
@@ -0,0 +1,30 @@
+#include "HeadBodyEditor.h"
+#include "Common.h"
+#include "cru/ui/ThemeManager.h"
+#include "cru/ui/controls/FlexLayout.h"
+
+namespace cru::theme_builder::components {
+HeadBodyEditor::HeadBodyEditor() {
+ border_.SetChild(&container_);
+ border_.SetBackgroundBrush(CreateRandomEditorBackgroundBrush());
+
+ container_.SetFlexDirection(ui::controls::FlexDirection::Vertical);
+ container_.AddChild(&head_container_);
+ container_.SetItemCrossAlign(ui::controls::FlexCrossAlignment::Stretch);
+ head_container_.SetFlexDirection(ui::controls::FlexDirection::Horizontal);
+ head_container_.AddChild(&label_);
+
+ remove_button_.GetStyleRuleSet()->SetParent(
+ ui::ThemeManager::GetInstance()->GetResourceStyleRuleSet(
+ u"cru.theme_builder.icon-button.style"));
+ remove_button_.SetIconWithSvgPathDataStringResourceKey(u"icon.close",
+ {0, 0, 16, 16});
+ remove_button_.SetIconFillColor(ui::colors::red);
+ head_container_.AddChild(&remove_button_);
+
+ remove_button_.ClickEvent()->AddSpyOnlyHandler(
+ [this] { remove_event_.Raise(nullptr); });
+}
+
+HeadBodyEditor::~HeadBodyEditor() {}
+} // namespace cru::theme_builder::components
diff --git a/src/ThemeBuilder/components/HeadBodyEditor.h b/src/ThemeBuilder/components/HeadBodyEditor.h
new file mode 100644
index 00000000..8119724f
--- /dev/null
+++ b/src/ThemeBuilder/components/HeadBodyEditor.h
@@ -0,0 +1,36 @@
+#pragma once
+#include "Editor.h"
+#include "cru/common/Event.h"
+#include "cru/ui/controls/Container.h"
+#include "cru/ui/controls/FlexLayout.h"
+#include "cru/ui/controls/IconButton.h"
+#include "cru/ui/controls/TextBlock.h"
+#include "cru/ui/style/Styler.h"
+
+namespace cru::theme_builder::components {
+class HeadBodyEditor : public Editor {
+ public:
+ HeadBodyEditor();
+ ~HeadBodyEditor() override;
+
+ public:
+ ui::controls::Control* GetRootControl() override { return &border_; }
+
+ ui::controls::FlexLayout* GetContainer() { return &container_; }
+ ui::controls::FlexLayout* GetHeadContainer() { return &head_container_; }
+
+ String GetLabel() const { return label_.GetText(); }
+ void SetLabel(String label) { label_.SetText(std::move(label)); }
+
+ IEvent<std::nullptr_t>* RemoveEvent() { return &remove_event_; }
+
+ private:
+ ui::controls::Container border_;
+ ui::controls::FlexLayout container_;
+ ui::controls::FlexLayout head_container_;
+ ui::controls::TextBlock label_;
+ ui::controls::IconButton remove_button_;
+
+ Event<std::nullptr_t> remove_event_;
+};
+} // namespace cru::theme_builder::components
diff --git a/src/ThemeBuilder/components/MainWindow.cpp b/src/ThemeBuilder/components/MainWindow.cpp
new file mode 100644
index 00000000..0c78ef25
--- /dev/null
+++ b/src/ThemeBuilder/components/MainWindow.cpp
@@ -0,0 +1,37 @@
+#include "MainWindow.h"
+#include "cru/ui/Base.h"
+#include "cru/ui/controls/StackLayout.h"
+#include "cru/ui/controls/TextBlock.h"
+
+namespace cru::theme_builder::components {
+using namespace cru::ui;
+using namespace cru::ui::controls;
+using namespace cru::platform::gui;
+
+MainWindow::MainWindow() {
+ window_.GetNativeWindow()->SetTitle(u"CruUI Theme Builder");
+ main_layout_.SetFlexDirection(FlexDirection::Horizontal);
+ window_.AddChild(&main_layout_);
+ main_layout_.AddChild(&preview_layout_);
+
+ preview_button_text_.SetText(u"Preview");
+ preview_button_.SetChild(&preview_button_text_);
+ preview_layout_.AddChild(&preview_button_);
+ preview_layout_.SetChildLayoutData(
+ 0, StackChildLayoutData{Alignment::Center, Alignment::Center});
+
+ style_rule_set_editor_.BindStyleRuleSet(
+ preview_button_.GetStyleRuleSet()->GetParent());
+ main_layout_.AddChild(style_rule_set_editor_.GetRootControl());
+
+ main_layout_.SetChildLayoutData(0, {1, 0});
+ main_layout_.SetChildLayoutData(1, {0, 1});
+}
+
+MainWindow::~MainWindow() {}
+
+void MainWindow::Show() {
+ window_.GetNativeWindow()->SetVisibility(WindowVisibilityType::Show);
+ window_.GetNativeWindow()->SetToForeground();
+}
+} // namespace cru::theme_builder::components
diff --git a/src/ThemeBuilder/components/MainWindow.h b/src/ThemeBuilder/components/MainWindow.h
new file mode 100644
index 00000000..ede1c38f
--- /dev/null
+++ b/src/ThemeBuilder/components/MainWindow.h
@@ -0,0 +1,32 @@
+#pragma once
+#include "StyleRuleSetEditor.h"
+#include "cru/ui/components/Component.h"
+#include "cru/ui/controls/Button.h"
+#include "cru/ui/controls/FlexLayout.h"
+#include "cru/ui/controls/StackLayout.h"
+#include "cru/ui/controls/TextBlock.h"
+#include "cru/ui/controls/Window.h"
+
+namespace cru::theme_builder::components {
+class MainWindow : public ui::components::Component {
+ public:
+ MainWindow();
+
+ CRU_DELETE_COPY(MainWindow)
+ CRU_DELETE_MOVE(MainWindow)
+
+ ~MainWindow() override;
+
+ ui::controls::Control* GetRootControl() override { return &window_; }
+
+ void Show();
+
+ private:
+ ui::controls::Window window_;
+ ui::controls::FlexLayout main_layout_;
+ ui::controls::StackLayout preview_layout_;
+ ui::controls::Button preview_button_;
+ ui::controls::TextBlock preview_button_text_;
+ StyleRuleSetEditor style_rule_set_editor_;
+};
+} // namespace cru::theme_builder::components
diff --git a/src/ThemeBuilder/components/StyleRuleEditor.cpp b/src/ThemeBuilder/components/StyleRuleEditor.cpp
new file mode 100644
index 00000000..dcb33184
--- /dev/null
+++ b/src/ThemeBuilder/components/StyleRuleEditor.cpp
@@ -0,0 +1,62 @@
+#include "StyleRuleEditor.h"
+#include "Common.h"
+#include "conditions/ConditionEditor.h"
+#include "cru/ui/ThemeManager.h"
+#include "cru/ui/style/StyleRule.h"
+
+namespace cru::theme_builder::components {
+StyleRuleEditor::StyleRuleEditor() {
+ container_.SetChild(&main_layout_);
+ container_.SetBackgroundBrush(CreateRandomEditorBackgroundBrush());
+
+ main_layout_.SetFlexDirection(ui::controls::FlexDirection::Vertical);
+ main_layout_.SetItemCrossAlign(ui::controls::FlexCrossAlignment::Start);
+
+ main_layout_.AddChild(&head_layout_);
+
+ label_.SetText(u"Style Rule");
+ head_layout_.AddChild(&label_);
+ head_layout_.AddChild(&remove_button_);
+
+ remove_button_.GetStyleRuleSet()->SetParent(
+ ui::ThemeManager::GetInstance()->GetResourceStyleRuleSet(
+ u"cru.theme_builder.icon-button.style"));
+ remove_button_.SetIconWithSvgPathDataStringResourceKey(u"icon.close",
+ {0, 0, 16, 16});
+ remove_button_.SetIconFillColor(ui::colors::red);
+
+ main_layout_.AddChild(&body_layout_);
+ body_layout_.SetFlexDirection(ui::controls::FlexDirection::Vertical);
+ body_layout_.SetItemCrossAlign(ui::controls::FlexCrossAlignment::Start);
+ body_layout_.SetMargin({10, 0, 0, 0});
+
+ remove_button_.ClickEvent()->AddSpyOnlyHandler(
+ [this] { remove_event_.Raise(nullptr); });
+}
+
+StyleRuleEditor::~StyleRuleEditor() {}
+
+ui::style::StyleRule StyleRuleEditor::GetValue() const {
+ return ui::style::StyleRule(condition_editor_->GetCondition(),
+ styler_editor_->GetStyler());
+}
+
+void StyleRuleEditor::SetValue(const ui::style::StyleRule& style_rule,
+ bool trigger_change) {
+ body_layout_.ClearChildren();
+ condition_editor_ =
+ components::conditions::CreateConditionEditor(style_rule.GetCondition());
+ styler_editor_ =
+ components::stylers::CreateStylerEditor(style_rule.GetStyler());
+ body_layout_.AddChild(condition_editor_->GetRootControl());
+ body_layout_.AddChild(styler_editor_->GetRootControl());
+ condition_editor_->ChangeEvent()->AddSpyOnlyHandler(
+ [this] { change_event_.Raise(nullptr); });
+ styler_editor_->ChangeEvent()->AddSpyOnlyHandler(
+ [this] { change_event_.Raise(nullptr); });
+
+ if (trigger_change) {
+ change_event_.Raise(nullptr);
+ }
+}
+} // namespace cru::theme_builder::components
diff --git a/src/ThemeBuilder/components/StyleRuleEditor.h b/src/ThemeBuilder/components/StyleRuleEditor.h
new file mode 100644
index 00000000..8e3db3de
--- /dev/null
+++ b/src/ThemeBuilder/components/StyleRuleEditor.h
@@ -0,0 +1,45 @@
+#pragma once
+#include "conditions/ConditionEditor.h"
+#include "cru/ui/components/Component.h"
+#include "cru/ui/controls/Button.h"
+#include "cru/ui/controls/Control.h"
+#include "cru/ui/controls/FlexLayout.h"
+#include "cru/ui/controls/IconButton.h"
+#include "cru/ui/controls/TextBlock.h"
+#include "cru/ui/style/StyleRule.h"
+#include "stylers/StylerEditor.h"
+
+namespace cru::theme_builder::components {
+class StyleRuleEditor : public ui::components::Component {
+ public:
+ StyleRuleEditor();
+
+ CRU_DELETE_COPY(StyleRuleEditor)
+ CRU_DELETE_MOVE(StyleRuleEditor)
+
+ ~StyleRuleEditor() override;
+
+ public:
+ ui::controls::Control* GetRootControl() override { return &container_; }
+
+ ui::style::StyleRule GetValue() const;
+ void SetValue(const ui::style::StyleRule& style_rule,
+ bool trigger_change = true);
+
+ IEvent<std::nullptr_t>* ChangeEvent() { return &change_event_; }
+ IEvent<std::nullptr_t>* RemoveEvent() { return &remove_event_; }
+
+ private:
+ ui::controls::Container container_;
+ ui::controls::FlexLayout main_layout_;
+ ui::controls::TextBlock label_;
+ ui::controls::FlexLayout head_layout_;
+ ui::controls::IconButton remove_button_;
+ ui::controls::FlexLayout body_layout_;
+ std::unique_ptr<components::conditions::ConditionEditor> condition_editor_;
+ std::unique_ptr<components::stylers::StylerEditor> styler_editor_;
+
+ Event<std::nullptr_t> change_event_;
+ Event<std::nullptr_t> remove_event_;
+};
+} // namespace cru::theme_builder::components
diff --git a/src/ThemeBuilder/components/StyleRuleSetEditor.cpp b/src/ThemeBuilder/components/StyleRuleSetEditor.cpp
new file mode 100644
index 00000000..8cf5af6d
--- /dev/null
+++ b/src/ThemeBuilder/components/StyleRuleSetEditor.cpp
@@ -0,0 +1,123 @@
+#include "StyleRuleSetEditor.h"
+#include "cru/common/Exception.h"
+#include "cru/common/String.h"
+#include "cru/ui/DeleteLater.h"
+#include "cru/ui/ThemeManager.h"
+#include "cru/ui/controls/FlexLayout.h"
+#include "cru/ui/model/IListChangeNotify.h"
+#include "cru/ui/render/FlexLayoutRenderObject.h"
+#include "cru/ui/style/Condition.h"
+#include "cru/ui/style/Styler.h"
+
+namespace cru::theme_builder::components {
+using namespace cru::ui::controls;
+StyleRuleSetEditor::StyleRuleSetEditor() {
+ scroll_view_.SetChild(&container_);
+
+ container_.SetFlexDirection(ui::render::FlexDirection::Vertical);
+ container_.AddChild(&rules_layout_);
+ container_.AddChild(&add_button_);
+
+ rules_layout_.SetFlexDirection(ui::controls::FlexDirection::Vertical);
+ rules_layout_.SetItemCrossAlign(ui::controls::FlexCrossAlignment::Stretch);
+
+ add_button_.GetStyleRuleSet()->SetParent(
+ ui::ThemeManager::GetInstance()->GetResourceStyleRuleSet(
+ u"cru.theme_builder.icon-button.style"));
+ add_button_.SetIconWithSvgPathDataStringResourceKey(u"icon.plus",
+ {0, 0, 16, 16});
+ add_button_.SetPreferredSize({24, 24});
+ add_button_.SetPadding(ui::Thickness(2));
+ add_button_.SetIconFillColor(ui::colors::green);
+
+ add_button_.ClickEvent()->AddSpyOnlyHandler([this] {
+ auto rule_set = ui::style::StyleRule(ui::style::NoCondition::Create(),
+ ui::style::CompoundStyler::Create({}));
+ style_rule_set_->AddStyleRule(rule_set);
+ });
+}
+
+StyleRuleSetEditor::~StyleRuleSetEditor() {}
+
+void StyleRuleSetEditor::BindStyleRuleSet(
+ std::shared_ptr<ui::style::StyleRuleSet> rule_set) {
+ Expects(style_rule_set_ == nullptr && rule_set);
+ style_rule_set_ = std::move(rule_set);
+ UpdateView(style_rule_set_.get());
+ style_rule_set_->ListChangeEvent()->AddHandler(
+ [this](const ui::model::ListChange& change) {
+ UpdateView(style_rule_set_.get(), change);
+ });
+}
+
+Index StyleRuleSetEditor::IndexOfRuleEditor(StyleRuleEditor* editor) {
+ auto iter =
+ std::find_if(style_rule_editors_.cbegin(), style_rule_editors_.cend(),
+ [editor](const ui::DeleteLaterPtr<StyleRuleEditor>& p) {
+ return p.get() == editor;
+ });
+ return iter - style_rule_editors_.cbegin();
+}
+
+void StyleRuleSetEditor::UpdateView(
+ ui::style::StyleRuleSet* style_rule_set,
+ std::optional<ui::model::ListChange> change) {
+ if (change) {
+ switch (change->type) {
+ case ui::model::ListChangeType::kItemAdd: {
+ for (auto i = change->position; i < change->position + change->count;
+ ++i) {
+ const auto& rule = style_rule_set->GetStyleRule(i);
+ auto style_rule_editor = ui::MakeDeleteLaterPtr<StyleRuleEditor>();
+ style_rule_editor->SetValue(rule, false);
+ style_rule_editor->RemoveEvent()->AddSpyOnlyHandler(
+ [this, editor = style_rule_editor.get()] {
+ style_rule_set_->RemoveStyleRule(IndexOfRuleEditor(editor));
+ });
+ style_rule_editor->ChangeEvent()->AddSpyOnlyHandler(
+ [this, editor = style_rule_editor.get()]() {
+ suppress_next_set_ = true;
+ style_rule_set_->SetStyleRule(IndexOfRuleEditor(editor),
+ editor->GetValue());
+ });
+ style_rule_editors_.insert(style_rule_editors_.cbegin() + i,
+ std::move(style_rule_editor));
+ rules_layout_.AddChildAt(style_rule_editors_.back()->GetRootControl(),
+ i);
+ }
+ break;
+ }
+ case ui::model::ListChangeType::kItemRemove: {
+ for (auto i = change->position; i < change->position + change->count;
+ ++i) {
+ style_rule_editors_.erase(style_rule_editors_.begin() + i);
+ }
+ break;
+ }
+ case ui::model::ListChangeType::kItemSet: {
+ if (suppress_next_set_) {
+ suppress_next_set_ = false;
+ break;
+ }
+ for (auto i = change->position; i < change->position + change->count;
+ ++i) {
+ const auto& rule = style_rule_set->GetStyleRule(i);
+ style_rule_editors_[i]->SetValue(rule, false);
+ }
+ break;
+ }
+ case ui::model::ListChangeType::kItemMove: {
+ throw Exception(u"Not supported now!");
+ break;
+ }
+ case ui::model::ListChangeType::kClear: {
+ style_rule_editors_.clear();
+ }
+ }
+ } else {
+ UpdateView(style_rule_set, ui::model::ListChange::Clear());
+ UpdateView(style_rule_set,
+ ui::model::ListChange::ItemAdd(0, style_rule_set->GetSize()));
+ }
+}
+} // namespace cru::theme_builder::components
diff --git a/src/ThemeBuilder/components/StyleRuleSetEditor.h b/src/ThemeBuilder/components/StyleRuleSetEditor.h
new file mode 100644
index 00000000..03148889
--- /dev/null
+++ b/src/ThemeBuilder/components/StyleRuleSetEditor.h
@@ -0,0 +1,44 @@
+#pragma once
+#include "StyleRuleEditor.h"
+#include "cru/ui/DeleteLater.h"
+#include "cru/ui/components/Component.h"
+#include "cru/ui/controls/Button.h"
+#include "cru/ui/controls/Control.h"
+#include "cru/ui/controls/FlexLayout.h"
+#include "cru/ui/controls/ScrollView.h"
+#include "cru/ui/model/IListChangeNotify.h"
+#include "cru/ui/style/StyleRuleSet.h"
+
+namespace cru::theme_builder::components {
+class StyleRuleSetEditor : public ui::components::Component {
+ public:
+ StyleRuleSetEditor();
+
+ CRU_DELETE_COPY(StyleRuleSetEditor)
+ CRU_DELETE_MOVE(StyleRuleSetEditor)
+
+ ~StyleRuleSetEditor() override;
+
+ public:
+ ui::controls::Control* GetRootControl() override { return &scroll_view_; }
+
+ void BindStyleRuleSet(std::shared_ptr<ui::style::StyleRuleSet> rule_set);
+
+ private:
+ Index IndexOfRuleEditor(StyleRuleEditor* editor);
+
+ void UpdateView(ui::style::StyleRuleSet* style_rule_set,
+ std::optional<ui::model::ListChange> change = std::nullopt);
+
+ private:
+ std::shared_ptr<ui::style::StyleRuleSet> style_rule_set_;
+
+ ui::controls::ScrollView scroll_view_;
+ ui::controls::FlexLayout container_;
+ ui::controls::FlexLayout rules_layout_;
+ std::vector<ui::DeleteLaterPtr<StyleRuleEditor>> style_rule_editors_;
+ ui::controls::IconButton add_button_;
+
+ bool suppress_next_set_ = false;
+};
+} // namespace cru::theme_builder::components
diff --git a/src/ThemeBuilder/components/conditions/CheckedConditionEditor.cpp b/src/ThemeBuilder/components/conditions/CheckedConditionEditor.cpp
new file mode 100644
index 00000000..64370981
--- /dev/null
+++ b/src/ThemeBuilder/components/conditions/CheckedConditionEditor.cpp
@@ -0,0 +1,26 @@
+#include "CheckedConditionEditor.h"
+#include "cru/common/ClonablePtr.h"
+#include "cru/ui/style/Condition.h"
+
+namespace cru::theme_builder::components::conditions {
+CheckedConditionEditor::CheckedConditionEditor() {
+ SetLabel(u"Checked Condition");
+
+ checked_check_box_.SetLabel(u"Checked");
+ GetContainer()->AddChild(checked_check_box_.GetRootControl());
+
+ ConnectChangeEvent(checked_check_box_);
+}
+
+CheckedConditionEditor::~CheckedConditionEditor() {}
+
+ClonablePtr<ui::style::CheckedCondition> CheckedConditionEditor::GetValue()
+ const {
+ return ui::style::CheckedCondition::Create(checked_check_box_.GetValue());
+}
+
+void CheckedConditionEditor::SetValue(ui::style::CheckedCondition* value,
+ bool trigger_change) {
+ checked_check_box_.SetValue(value->IsChecked(), trigger_change);
+}
+} // namespace cru::theme_builder::components::conditions
diff --git a/src/ThemeBuilder/components/conditions/CheckedConditionEditor.h b/src/ThemeBuilder/components/conditions/CheckedConditionEditor.h
new file mode 100644
index 00000000..7cf14912
--- /dev/null
+++ b/src/ThemeBuilder/components/conditions/CheckedConditionEditor.h
@@ -0,0 +1,28 @@
+#pragma once
+#include "../properties/CheckBoxPropertyEditor.h"
+#include "ConditionEditor.h"
+#include "cru/common/ClonablePtr.h"
+#include "cru/ui/style/Condition.h"
+
+namespace cru::theme_builder::components::conditions {
+class CheckedConditionEditor : public ConditionEditor {
+ public:
+ CheckedConditionEditor();
+ ~CheckedConditionEditor() override;
+
+ public:
+ ClonablePtr<ui::style::CheckedCondition> GetValue() const;
+ void SetValue(ui::style::CheckedCondition* value, bool trigger_change = true);
+ void SetValue(const ClonablePtr<ui::style::CheckedCondition>& value,
+ bool trigger_change = true) {
+ SetValue(value.get(), trigger_change);
+ }
+
+ ClonablePtr<ui::style::Condition> GetCondition() override {
+ return GetValue();
+ }
+
+ private:
+ properties::CheckBoxPropertyEditor checked_check_box_;
+};
+} // namespace cru::theme_builder::components::conditions
diff --git a/src/ThemeBuilder/components/conditions/ClickStateConditionEditor.cpp b/src/ThemeBuilder/components/conditions/ClickStateConditionEditor.cpp
new file mode 100644
index 00000000..a8d5cc87
--- /dev/null
+++ b/src/ThemeBuilder/components/conditions/ClickStateConditionEditor.cpp
@@ -0,0 +1,69 @@
+#include "ClickStateConditionEditor.h"
+#include "cru/common/ClonablePtr.h"
+#include "cru/ui/helper/ClickDetector.h"
+#include "cru/ui/style/Condition.h"
+
+namespace cru::theme_builder::components::conditions {
+using ui::helper::ClickState;
+namespace {
+const std::vector<String> kClickStates{
+ u"None",
+ u"Hover",
+ u"Press",
+ u"PressInactive",
+};
+
+Index ConvertClickStateToIndex(ClickState click_state) {
+ switch (click_state) {
+ case ClickState::None:
+ return 0;
+ case ClickState::Hover:
+ return 1;
+ case ClickState::Press:
+ return 2;
+ case ClickState::PressInactive:
+ return 3;
+ }
+ return -1;
+}
+
+ClickState ConvertIndexToClickState(Index index) {
+ switch (index) {
+ case 0:
+ return ClickState::None;
+ case 1:
+ return ClickState::Hover;
+ case 2:
+ return ClickState::Press;
+ case 3:
+ return ClickState::PressInactive;
+ }
+ return ClickState::None;
+}
+} // namespace
+
+ClickStateConditionEditor::ClickStateConditionEditor() {
+ SetLabel(u"Click State Condition");
+ GetContainer()->AddChild(click_state_select_.GetRootControl());
+
+ click_state_select_.SetLabel(u"Click State");
+ click_state_select_.SetItems(kClickStates);
+ click_state_select_.SetSelectedIndex(0, false);
+
+ ConnectChangeEvent(click_state_select_);
+}
+
+ClickStateConditionEditor::~ClickStateConditionEditor() {}
+
+ClonablePtr<ui::style::ClickStateCondition>
+ClickStateConditionEditor::GetValue() const {
+ return ui::style::ClickStateCondition::Create(
+ ConvertIndexToClickState(click_state_select_.GetSelectedIndex()));
+}
+
+void ClickStateConditionEditor::SetValue(ui::style::ClickStateCondition* value,
+ bool trigger_change) {
+ click_state_select_.SetSelectedIndex(
+ ConvertClickStateToIndex(value->GetClickState()), trigger_change);
+}
+} // namespace cru::theme_builder::components::conditions
diff --git a/src/ThemeBuilder/components/conditions/ClickStateConditionEditor.h b/src/ThemeBuilder/components/conditions/ClickStateConditionEditor.h
new file mode 100644
index 00000000..454a1346
--- /dev/null
+++ b/src/ThemeBuilder/components/conditions/ClickStateConditionEditor.h
@@ -0,0 +1,30 @@
+#pragma once
+#include "../properties/SelectPropertyEditor.h"
+#include "ConditionEditor.h"
+#include "cru/common/ClonablePtr.h"
+#include "cru/common/Event.h"
+#include "cru/ui/style/Condition.h"
+
+namespace cru::theme_builder::components::conditions {
+class ClickStateConditionEditor : public ConditionEditor {
+ public:
+ ClickStateConditionEditor();
+ ~ClickStateConditionEditor();
+
+ public:
+ ClonablePtr<ui::style::ClickStateCondition> GetValue() const;
+ void SetValue(ui::style::ClickStateCondition* value,
+ bool trigger_change = true);
+ void SetValue(const ClonablePtr<ui::style::ClickStateCondition>& value,
+ bool trigger_change = true) {
+ SetValue(value.get(), trigger_change);
+ }
+
+ ClonablePtr<ui::style::Condition> GetCondition() override {
+ return GetValue();
+ }
+
+ private:
+ properties::SelectPropertyEditor click_state_select_;
+};
+} // namespace cru::theme_builder::components::conditions
diff --git a/src/ThemeBuilder/components/conditions/CompoundConditionEditor.cpp b/src/ThemeBuilder/components/conditions/CompoundConditionEditor.cpp
new file mode 100644
index 00000000..69b8ed02
--- /dev/null
+++ b/src/ThemeBuilder/components/conditions/CompoundConditionEditor.cpp
@@ -0,0 +1,109 @@
+#include "CompoundConditionEditor.h"
+#include "CheckedConditionEditor.h"
+#include "ClickStateConditionEditor.h"
+#include "ConditionEditor.h"
+#include "FocusConditionEditor.h"
+#include "NoConditionEditor.h"
+#include "cru/common/ClonablePtr.h"
+#include "cru/platform/Color.h"
+#include "cru/ui/Base.h"
+#include "cru/ui/ThemeManager.h"
+#include "cru/ui/controls/FlexLayout.h"
+#include "cru/ui/style/Condition.h"
+
+namespace cru::theme_builder::components::conditions {
+
+CompoundConditionEditor::CompoundConditionEditor() {
+ SetLabel(u"Compound Condition");
+
+ GetContainer()->AddChild(&children_container_);
+ children_container_.SetMargin({10, 0, 0, 0});
+ children_container_.SetFlexDirection(ui::controls::FlexDirection::Vertical);
+ children_container_.SetItemCrossAlign(
+ ui::controls::FlexCrossAlignment::Start);
+
+ GetHeadContainer()->AddChild(add_child_button_.GetRootControl());
+
+ add_child_button_.GetButton()->GetStyleRuleSet()->SetParent(
+ ui::ThemeManager::GetInstance()->GetResourceStyleRuleSet(
+ u"cru.theme_builder.icon-button.style"));
+ add_child_button_.GetButton()->SetIconWithSvgPathDataStringResourceKey(
+ u"icon.plus", {0, 0, 16, 16});
+ add_child_button_.GetButton()->SetPreferredSize({24, 24});
+ add_child_button_.GetButton()->SetPadding(ui::Thickness(2));
+ add_child_button_.GetButton()->SetIconFillColor(ui::colors::green);
+ add_child_button_.SetMenuItems({u"And Condition", u"Or Condition",
+ u"Click State Condition", u"Focus Condition",
+ u"Checked Condition", u"No Condition"});
+ add_child_button_.MenuItemSelectedEvent()->AddHandler([this](Index index) {
+ std::unique_ptr<ConditionEditor> editor;
+ switch (index) {
+ case 0:
+ editor = std::make_unique<AndConditionEditor>();
+ break;
+ case 1:
+ editor = std::make_unique<OrConditionEditor>();
+ break;
+ case 2:
+ editor = std::make_unique<ClickStateConditionEditor>();
+ break;
+ case 3:
+ editor = std::make_unique<FocusConditionEditor>();
+ break;
+ case 4:
+ editor = std::make_unique<CheckedConditionEditor>();
+ break;
+ case 5:
+ editor = std::make_unique<NoConditionEditor>();
+ break;
+ default:
+ break;
+ }
+ if (editor) {
+ ConnectChangeEvent(editor.get());
+ editor->RemoveEvent()->AddSpyOnlyHandler([this, c = editor.get()] {
+ auto index = this->children_container_.IndexOf(c->GetRootControl());
+ this->children_.erase(this->children_.begin() + index);
+ this->children_container_.RemoveChildAt(index);
+ RaiseChangeEvent();
+ });
+ children_.push_back(std::move(editor));
+ children_container_.AddChild(children_.back()->GetRootControl());
+ RaiseChangeEvent();
+ }
+ });
+}
+
+CompoundConditionEditor::~CompoundConditionEditor() {}
+
+std::vector<ClonablePtr<ui::style::Condition>>
+CompoundConditionEditor::GetChildren() {
+ std::vector<ClonablePtr<ui::style::Condition>> children;
+ for (auto& child : children_) {
+ children.push_back(child->GetCondition());
+ }
+ return children;
+}
+
+void CompoundConditionEditor::SetChildren(
+ std::vector<ClonablePtr<ui::style::Condition>> children,
+ bool trigger_change) {
+ children_container_.ClearChildren();
+ children_.clear();
+ for (const auto& condition : children) {
+ auto editor = CreateConditionEditor(condition.get());
+ ConnectChangeEvent(editor.get());
+ editor->RemoveEvent()->AddSpyOnlyHandler([this, c = editor.get()] {
+ auto index = this->children_container_.IndexOf(c->GetRootControl());
+ this->children_.erase(this->children_.begin() + index);
+ this->children_container_.RemoveChildAt(index);
+ RaiseChangeEvent();
+ });
+ children_.push_back(std::move(editor));
+ children_container_.AddChild(children_.back()->GetRootControl());
+ }
+ if (trigger_change) {
+ RaiseChangeEvent();
+ }
+}
+} // namespace cru::theme_builder::components::conditions
diff --git a/src/ThemeBuilder/components/conditions/CompoundConditionEditor.h b/src/ThemeBuilder/components/conditions/CompoundConditionEditor.h
new file mode 100644
index 00000000..e1398514
--- /dev/null
+++ b/src/ThemeBuilder/components/conditions/CompoundConditionEditor.h
@@ -0,0 +1,72 @@
+#pragma once
+#include "ConditionEditor.h"
+#include "cru/common/ClonablePtr.h"
+#include "cru/common/Event.h"
+#include "cru/ui/components/Component.h"
+#include "cru/ui/components/PopupButton.h"
+#include "cru/ui/controls/Button.h"
+#include "cru/ui/controls/FlexLayout.h"
+#include "cru/ui/controls/TextBlock.h"
+#include "cru/ui/style/Condition.h"
+
+namespace cru::theme_builder::components::conditions {
+class CompoundConditionEditor : public ConditionEditor {
+ public:
+ CompoundConditionEditor();
+ ~CompoundConditionEditor();
+
+ protected:
+ std::vector<ClonablePtr<ui::style::Condition>> GetChildren();
+ void SetChildren(std::vector<ClonablePtr<ui::style::Condition>> children,
+ bool trigger_change = true);
+
+ private:
+ ui::components::PopupMenuIconButton add_child_button_;
+ ui::controls::FlexLayout children_container_;
+ std::vector<ui::DeleteLaterPtr<ConditionEditor>> children_;
+};
+
+class AndConditionEditor : public CompoundConditionEditor {
+ public:
+ AndConditionEditor() = default;
+ ~AndConditionEditor() override = default;
+
+ public:
+ ClonablePtr<ui::style::AndCondition> GetValue() {
+ return ui::style::AndCondition::Create(GetChildren());
+ }
+ void SetValue(ui::style::AndCondition* value, bool trigger_change = true) {
+ SetChildren(value->GetChildren(), trigger_change);
+ }
+ void SetValue(const ClonablePtr<ui::style::AndCondition>& value,
+ bool trigger_change = true) {
+ SetValue(value.get(), trigger_change);
+ }
+
+ ClonablePtr<ui::style::Condition> GetCondition() override {
+ return GetValue();
+ }
+};
+
+class OrConditionEditor : public CompoundConditionEditor {
+ public:
+ OrConditionEditor() = default;
+ ~OrConditionEditor() override = default;
+
+ public:
+ ClonablePtr<ui::style::OrCondition> GetValue() {
+ return ui::style::OrCondition::Create(GetChildren());
+ }
+ void SetValue(ui::style::OrCondition* value, bool trigger_change = true) {
+ SetChildren(value->GetChildren(), trigger_change);
+ }
+ void SetValue(const ClonablePtr<ui::style::OrCondition>& value,
+ bool trigger_change = true) {
+ SetValue(value.get(), trigger_change);
+ }
+
+ ClonablePtr<ui::style::Condition> GetCondition() override {
+ return GetValue();
+ }
+};
+} // namespace cru::theme_builder::components::conditions
diff --git a/src/ThemeBuilder/components/conditions/ConditionEditor.cpp b/src/ThemeBuilder/components/conditions/ConditionEditor.cpp
new file mode 100644
index 00000000..5b79c639
--- /dev/null
+++ b/src/ThemeBuilder/components/conditions/ConditionEditor.cpp
@@ -0,0 +1,50 @@
+#include "ConditionEditor.h"
+#include "../Common.h"
+#include "CheckedConditionEditor.h"
+#include "ClickStateConditionEditor.h"
+#include "CompoundConditionEditor.h"
+#include "FocusConditionEditor.h"
+#include "NoConditionEditor.h"
+#include "cru/common/Exception.h"
+#include "cru/ui/controls/FlexLayout.h"
+
+namespace cru::theme_builder::components::conditions {
+ConditionEditor::ConditionEditor() {}
+
+ConditionEditor::~ConditionEditor() {}
+
+std::unique_ptr<ConditionEditor> CreateConditionEditor(
+ ui::style::Condition* condition) {
+ if (auto and_condition = dynamic_cast<ui::style::AndCondition*>(condition)) {
+ auto result = std::make_unique<AndConditionEditor>();
+ result->SetValue(and_condition);
+ return result;
+ } else if (auto or_condition =
+ dynamic_cast<ui::style::OrCondition*>(condition)) {
+ auto result = std::make_unique<OrConditionEditor>();
+ result->SetValue(or_condition);
+ return result;
+ } else if (auto no_condition =
+ dynamic_cast<ui::style::NoCondition*>(condition)) {
+ auto result = std::make_unique<NoConditionEditor>();
+ return result;
+ } else if (auto click_state_condition =
+ dynamic_cast<ui::style::ClickStateCondition*>(condition)) {
+ auto result = std::make_unique<ClickStateConditionEditor>();
+ result->SetValue(click_state_condition);
+ return result;
+ } else if (auto focus_condition =
+ dynamic_cast<ui::style::FocusCondition*>(condition)) {
+ auto result = std::make_unique<FocusConditionEditor>();
+ result->SetValue(focus_condition);
+ return result;
+ } else if (auto checked_condition =
+ dynamic_cast<ui::style::CheckedCondition*>(condition)) {
+ auto result = std::make_unique<CheckedConditionEditor>();
+ result->SetValue(checked_condition);
+ return result;
+ } else {
+ throw Exception(u"Unknown condition type");
+ }
+}
+} // namespace cru::theme_builder::components::conditions
diff --git a/src/ThemeBuilder/components/conditions/ConditionEditor.h b/src/ThemeBuilder/components/conditions/ConditionEditor.h
new file mode 100644
index 00000000..f20132f6
--- /dev/null
+++ b/src/ThemeBuilder/components/conditions/ConditionEditor.h
@@ -0,0 +1,17 @@
+#pragma once
+#include "../HeadBodyEditor.h"
+#include "cru/ui/style/Condition.h"
+
+namespace cru::theme_builder::components::conditions {
+class ConditionEditor : public HeadBodyEditor {
+ public:
+ ConditionEditor();
+ ~ConditionEditor() override;
+
+ public:
+ virtual ClonablePtr<ui::style::Condition> GetCondition() = 0;
+};
+
+std::unique_ptr<ConditionEditor> CreateConditionEditor(
+ ui::style::Condition* condition);
+} // namespace cru::theme_builder::components::conditions
diff --git a/src/ThemeBuilder/components/conditions/FocusConditionEditor.cpp b/src/ThemeBuilder/components/conditions/FocusConditionEditor.cpp
new file mode 100644
index 00000000..1fb99d64
--- /dev/null
+++ b/src/ThemeBuilder/components/conditions/FocusConditionEditor.cpp
@@ -0,0 +1,25 @@
+#include "FocusConditionEditor.h"
+#include "cru/common/ClonablePtr.h"
+#include "cru/ui/style/Condition.h"
+
+namespace cru::theme_builder::components::conditions {
+FocusConditionEditor::FocusConditionEditor() {
+ SetLabel(u"Focus Condition");
+ GetContainer()->AddChild(focus_check_box_.GetRootControl());
+
+ focus_check_box_.SetLabel(u"Focus");
+
+ ConnectChangeEvent(focus_check_box_);
+}
+
+FocusConditionEditor::~FocusConditionEditor() {}
+
+ClonablePtr<ui::style::FocusCondition> FocusConditionEditor::GetValue() const {
+ return ui::style::FocusCondition::Create(focus_check_box_.GetValue());
+}
+
+void FocusConditionEditor::SetValue(ui::style::FocusCondition* value,
+ bool trigger_change) {
+ focus_check_box_.SetValue(value->IsHasFocus(), trigger_change);
+}
+} // namespace cru::theme_builder::components::conditions
diff --git a/src/ThemeBuilder/components/conditions/FocusConditionEditor.h b/src/ThemeBuilder/components/conditions/FocusConditionEditor.h
new file mode 100644
index 00000000..1faf4d7d
--- /dev/null
+++ b/src/ThemeBuilder/components/conditions/FocusConditionEditor.h
@@ -0,0 +1,28 @@
+#pragma once
+#include "../properties/CheckBoxPropertyEditor.h"
+#include "ConditionEditor.h"
+#include "cru/common/ClonablePtr.h"
+#include "cru/ui/style/Condition.h"
+
+namespace cru::theme_builder::components::conditions {
+class FocusConditionEditor : public ConditionEditor {
+ public:
+ FocusConditionEditor();
+ ~FocusConditionEditor() override;
+
+ public:
+ ClonablePtr<ui::style::FocusCondition> GetValue() const;
+ void SetValue(ui::style::FocusCondition* value, bool trigger_change = true);
+ void SetValue(const ClonablePtr<ui::style::FocusCondition>& value,
+ bool trigger_change = true) {
+ SetValue(value.get(), trigger_change);
+ }
+
+ ClonablePtr<ui::style::Condition> GetCondition() override {
+ return GetValue();
+ }
+
+ private:
+ properties::CheckBoxPropertyEditor focus_check_box_;
+};
+} // namespace cru::theme_builder::components::conditions
diff --git a/src/ThemeBuilder/components/conditions/NoConditionEditor.cpp b/src/ThemeBuilder/components/conditions/NoConditionEditor.cpp
new file mode 100644
index 00000000..a5087159
--- /dev/null
+++ b/src/ThemeBuilder/components/conditions/NoConditionEditor.cpp
@@ -0,0 +1,7 @@
+#include "NoConditionEditor.h"
+
+namespace cru::theme_builder::components::conditions {
+NoConditionEditor::NoConditionEditor() { SetLabel(u"No condition"); }
+
+NoConditionEditor::~NoConditionEditor() {}
+} // namespace cru::theme_builder::components::conditions
diff --git a/src/ThemeBuilder/components/conditions/NoConditionEditor.h b/src/ThemeBuilder/components/conditions/NoConditionEditor.h
new file mode 100644
index 00000000..19616319
--- /dev/null
+++ b/src/ThemeBuilder/components/conditions/NoConditionEditor.h
@@ -0,0 +1,17 @@
+#pragma once
+#include "ConditionEditor.h"
+#include "cru/common/ClonablePtr.h"
+#include "cru/ui/style/Condition.h"
+
+namespace cru::theme_builder::components::conditions {
+class NoConditionEditor : public ConditionEditor {
+ public:
+ NoConditionEditor();
+ ~NoConditionEditor() override;
+
+ public:
+ ClonablePtr<ui::style::Condition> GetCondition() override {
+ return ui::style::NoCondition::Create();
+ }
+};
+} // namespace cru::theme_builder::components::conditions
diff --git a/src/ThemeBuilder/components/properties/CheckBoxPropertyEditor.cpp b/src/ThemeBuilder/components/properties/CheckBoxPropertyEditor.cpp
new file mode 100644
index 00000000..fb6f4705
--- /dev/null
+++ b/src/ThemeBuilder/components/properties/CheckBoxPropertyEditor.cpp
@@ -0,0 +1,19 @@
+#include "CheckBoxPropertyEditor.h"
+
+namespace cru::theme_builder::components::properties {
+CheckBoxPropertyEditor::CheckBoxPropertyEditor() {
+ container_.SetFlexDirection(ui::controls::FlexDirection::Horizontal);
+ container_.AddChild(&label_);
+ container_.AddChild(&check_box_);
+
+ check_box_.CheckedChangeEvent()->AddSpyOnlyHandler(
+ [this] { RaiseChangeEvent(); });
+}
+
+CheckBoxPropertyEditor::~CheckBoxPropertyEditor() {}
+
+void CheckBoxPropertyEditor::SetValue(bool value, bool trigger_change) {
+ if (!trigger_change) SuppressNextChangeEvent();
+ check_box_.SetChecked(value);
+}
+} // namespace cru::theme_builder::components::properties
diff --git a/src/ThemeBuilder/components/properties/CheckBoxPropertyEditor.h b/src/ThemeBuilder/components/properties/CheckBoxPropertyEditor.h
new file mode 100644
index 00000000..f78ed6c9
--- /dev/null
+++ b/src/ThemeBuilder/components/properties/CheckBoxPropertyEditor.h
@@ -0,0 +1,29 @@
+#pragma once
+#include "../Editor.h"
+#include "cru/ui/controls/CheckBox.h"
+#include "cru/ui/controls/FlexLayout.h"
+#include "cru/ui/controls/TextBlock.h"
+
+namespace cru::theme_builder::components::properties {
+class CheckBoxPropertyEditor : public Editor {
+ public:
+ using PropertyType = bool;
+
+ CheckBoxPropertyEditor();
+ ~CheckBoxPropertyEditor() override;
+
+ public:
+ ui::controls::Control* GetRootControl() override { return &container_; }
+
+ String GetLabel() const { return label_.GetText(); }
+ void SetLabel(String label) { label_.SetText(std::move(label)); }
+
+ bool GetValue() const { return check_box_.IsChecked(); }
+ void SetValue(bool value, bool trigger_change = true);
+
+ private:
+ ui::controls::FlexLayout container_;
+ ui::controls::TextBlock label_;
+ ui::controls::CheckBox check_box_;
+};
+} // namespace cru::theme_builder::components::properties
diff --git a/src/ThemeBuilder/components/properties/ColorPropertyEditor.cpp b/src/ThemeBuilder/components/properties/ColorPropertyEditor.cpp
new file mode 100644
index 00000000..e9e486ac
--- /dev/null
+++ b/src/ThemeBuilder/components/properties/ColorPropertyEditor.cpp
@@ -0,0 +1,48 @@
+#include "ColorPropertyEditor.h"
+#include "cru/platform/graphics/Factory.h"
+#include "cru/ui/Base.h"
+#include "cru/ui/ThemeManager.h"
+
+namespace cru::theme_builder::components::properties {
+ColorPropertyEditor::ColorPropertyEditor() {
+ container_.AddChild(&label_);
+ container_.AddChild(&color_cube_);
+ container_.AddChild(&color_text_);
+
+ color_cube_.SetBorderEnabled(true);
+ color_cube_.GetStyleRuleSet()->SetParent(
+ ui::ThemeManager::GetInstance()->GetResourceStyleRuleSet(
+ u"cru.theme_builder.color_cube.style"));
+
+ color_cube_brush_ = platform::gui::IUiApplication::GetInstance()
+ ->GetGraphicsFactory()
+ ->CreateSolidColorBrush(color_);
+
+ color_cube_.SetForegroundBrush(color_cube_brush_);
+
+ color_text_.SetText(color_.ToString());
+ color_text_.SetMargin(ui::Thickness(10, 0, 0, 0));
+
+ color_text_.TextChangeEvent()->AddHandler([this](std::nullptr_t) {
+ auto text = color_text_.GetTextView();
+ auto color = ui::Color::Parse(text);
+ if (color) {
+ color_ = *color;
+ color_cube_brush_->SetColor(*color);
+ is_color_text_valid_ = true;
+ RaiseChangeEvent();
+ } else {
+ is_color_text_valid_ = false;
+ // TODO: Show error!
+ }
+ });
+}
+
+ColorPropertyEditor::~ColorPropertyEditor() {}
+
+void ColorPropertyEditor::SetValue(const ui::Color &color,
+ bool trigger_change) {
+ if (!trigger_change) SuppressNextChangeEvent();
+ color_text_.SetText(color.ToString());
+}
+} // namespace cru::theme_builder::components::properties
diff --git a/src/ThemeBuilder/components/properties/ColorPropertyEditor.h b/src/ThemeBuilder/components/properties/ColorPropertyEditor.h
new file mode 100644
index 00000000..aa6cfcfa
--- /dev/null
+++ b/src/ThemeBuilder/components/properties/ColorPropertyEditor.h
@@ -0,0 +1,36 @@
+#pragma once
+#include "../Editor.h"
+#include "cru/platform/graphics/Base.h"
+#include "cru/ui/controls/Container.h"
+#include "cru/ui/controls/FlexLayout.h"
+#include "cru/ui/controls/TextBlock.h"
+#include "cru/ui/controls/TextBox.h"
+
+namespace cru::theme_builder::components::properties {
+class ColorPropertyEditor : public Editor {
+ public:
+ using PropertyType = ui::Color;
+
+ ColorPropertyEditor();
+ ~ColorPropertyEditor() override;
+
+ public:
+ ui::controls::Control* GetRootControl() override { return &container_; }
+
+ String GetLabel() const { return label_.GetText(); }
+ void SetLabel(String label) { label_.SetText(std::move(label)); }
+
+ ui::Color GetValue() const { return color_; }
+ void SetValue(const ui::Color& color, bool trigger_change = true);
+
+ private:
+ ui::Color color_ = ui::colors::transparent;
+
+ ui::controls::FlexLayout container_;
+ ui::controls::TextBlock label_;
+ ui::controls::Container color_cube_;
+ std::shared_ptr<platform::graphics::ISolidColorBrush> color_cube_brush_;
+ ui::controls::TextBox color_text_;
+ bool is_color_text_valid_;
+};
+} // namespace cru::theme_builder::components::properties
diff --git a/src/ThemeBuilder/components/properties/CornerRadiusPropertyEditor.cpp b/src/ThemeBuilder/components/properties/CornerRadiusPropertyEditor.cpp
new file mode 100644
index 00000000..fc86b0ed
--- /dev/null
+++ b/src/ThemeBuilder/components/properties/CornerRadiusPropertyEditor.cpp
@@ -0,0 +1,42 @@
+#include "CornerRadiusPropertyEditor.h"
+#include "cru/ui/Base.h"
+#include "cru/ui/controls/FlexLayout.h"
+
+namespace cru::theme_builder::components::properties {
+CornerRadiusPropertyEditor::CornerRadiusPropertyEditor() {
+ container_.SetItemCrossAlign(ui::controls::FlexCrossAlignment::Start);
+
+ left_top_editor_.SetLabel(u"⌜");
+ right_top_editor_.SetLabel(u"⌝");
+ left_bottom_editor_.SetLabel(u"⌞");
+ right_bottom_editor_.SetLabel(u"⌟");
+
+ container_.SetFlexDirection(ui::controls::FlexDirection::Vertical);
+ container_.AddChild(left_top_editor_.GetRootControl());
+ container_.AddChild(right_top_editor_.GetRootControl());
+ container_.AddChild(left_bottom_editor_.GetRootControl());
+ container_.AddChild(right_bottom_editor_.GetRootControl());
+
+ ConnectChangeEvent(left_top_editor_);
+ ConnectChangeEvent(right_top_editor_);
+ ConnectChangeEvent(left_bottom_editor_);
+ ConnectChangeEvent(right_bottom_editor_);
+}
+
+CornerRadiusPropertyEditor::~CornerRadiusPropertyEditor() {}
+
+ui::CornerRadius CornerRadiusPropertyEditor::GetValue() const {
+ return ui::CornerRadius(
+ left_top_editor_.GetValue(), right_top_editor_.GetValue(),
+ left_bottom_editor_.GetValue(), right_bottom_editor_.GetValue());
+}
+
+void CornerRadiusPropertyEditor::SetValue(const ui::CornerRadius& corner_radius,
+ bool trigger_change) {
+ left_top_editor_.SetValue(corner_radius.left_top, false);
+ right_top_editor_.SetValue(corner_radius.right_top, false);
+ left_bottom_editor_.SetValue(corner_radius.left_bottom, false);
+ right_bottom_editor_.SetValue(corner_radius.right_bottom, false);
+ if (trigger_change) RaiseChangeEvent();
+}
+} // namespace cru::theme_builder::components::properties
diff --git a/src/ThemeBuilder/components/properties/CornerRadiusPropertyEditor.h b/src/ThemeBuilder/components/properties/CornerRadiusPropertyEditor.h
new file mode 100644
index 00000000..6b6833d1
--- /dev/null
+++ b/src/ThemeBuilder/components/properties/CornerRadiusPropertyEditor.h
@@ -0,0 +1,28 @@
+#pragma once
+#include "../Editor.h"
+#include "PointPropertyEditor.h"
+#include "cru/ui/Base.h"
+#include "cru/ui/controls/FlexLayout.h"
+
+namespace cru::theme_builder::components::properties {
+class CornerRadiusPropertyEditor : public Editor {
+ public:
+ using PropertyType = ui::CornerRadius;
+
+ CornerRadiusPropertyEditor();
+ ~CornerRadiusPropertyEditor() override;
+
+ ui::controls::Control* GetRootControl() override { return &container_; }
+
+ ui::CornerRadius GetValue() const;
+ void SetValue(const ui::CornerRadius& corner_radius,
+ bool trigger_change = true);
+
+ private:
+ ui::controls::FlexLayout container_;
+ PointPropertyEditor left_top_editor_;
+ PointPropertyEditor right_top_editor_;
+ PointPropertyEditor left_bottom_editor_;
+ PointPropertyEditor right_bottom_editor_;
+};
+} // namespace cru::theme_builder::components::properties
diff --git a/src/ThemeBuilder/components/properties/FontPropertyEditor.cpp b/src/ThemeBuilder/components/properties/FontPropertyEditor.cpp
new file mode 100644
index 00000000..927ada7d
--- /dev/null
+++ b/src/ThemeBuilder/components/properties/FontPropertyEditor.cpp
@@ -0,0 +1,60 @@
+#include "FontPropertyEditor.h"
+#include "cru/platform/graphics/Factory.h"
+#include "cru/platform/graphics/Font.h"
+#include "cru/platform/gui/UiApplication.h"
+#include "cru/ui/controls/FlexLayout.h"
+#include "cru/ui/render/FlexLayoutRenderObject.h"
+
+namespace cru::theme_builder::components::properties {
+using namespace cru::ui::controls;
+
+FontPropertyEditor::FontPropertyEditor() {
+ main_container_.SetFlexDirection(FlexDirection::Horizontal);
+ main_container_.AddChild(&label_);
+ main_container_.AddChild(&right_container_);
+
+ right_container_.SetFlexDirection(FlexDirection::Vertical);
+ right_container_.AddChild(&font_family_container_);
+ right_container_.AddChild(&font_size_container_);
+
+ font_family_container_.SetFlexDirection(FlexDirection::Horizontal);
+ font_family_container_.AddChild(&font_family_label_);
+ font_family_container_.AddChild(&font_family_text_);
+ font_family_label_.SetText(u"Font Family");
+
+ font_size_container_.SetFlexDirection(FlexDirection::Horizontal);
+ font_size_container_.AddChild(&font_size_label_);
+ font_size_container_.AddChild(font_size_input_.GetRootControl());
+ font_size_label_.SetText(u"Font Size");
+ font_size_input_.SetMin(0.0f);
+
+ font_family_text_.TextChangeEvent()->AddSpyOnlyHandler(
+ [this] { RaiseChangeEvent(); });
+
+ font_size_input_.ChangeEvent()->AddSpyOnlyHandler(
+ [this] { RaiseChangeEvent(); });
+}
+
+FontPropertyEditor::~FontPropertyEditor() {}
+
+Control* FontPropertyEditor::GetRootControl() { return &main_container_; }
+
+std::shared_ptr<platform::graphics::IFont> FontPropertyEditor::GetValue()
+ const {
+ return platform::gui::IUiApplication::GetInstance()
+ ->GetGraphicsFactory()
+ ->CreateFont(font_family_text_.GetText(), font_size_input_.GetValue());
+}
+
+void FontPropertyEditor::SetValue(
+ std::shared_ptr<platform::graphics::IFont> value, bool trigger_change) {
+ SuppressNextChangeEvent();
+ font_family_text_.SetText(value->GetFontName());
+ SuppressNextChangeEvent();
+ font_size_input_.SetValue(value->GetFontSize());
+
+ if (trigger_change) {
+ RaiseChangeEvent();
+ }
+}
+} // namespace cru::theme_builder::components::properties
diff --git a/src/ThemeBuilder/components/properties/FontPropertyEditor.h b/src/ThemeBuilder/components/properties/FontPropertyEditor.h
new file mode 100644
index 00000000..d349f1f2
--- /dev/null
+++ b/src/ThemeBuilder/components/properties/FontPropertyEditor.h
@@ -0,0 +1,38 @@
+#pragma once
+#include "../Editor.h"
+#include "cru/platform/graphics/Font.h"
+#include "cru/ui/components/Input.h"
+#include "cru/ui/controls/Control.h"
+#include "cru/ui/controls/FlexLayout.h"
+#include "cru/ui/controls/TextBlock.h"
+#include "cru/ui/controls/TextBox.h"
+
+namespace cru::theme_builder::components::properties {
+class FontPropertyEditor : public Editor {
+ public:
+ using PropertyType = std::shared_ptr<platform::graphics::IFont>;
+
+ FontPropertyEditor();
+ ~FontPropertyEditor() override;
+
+ ui::controls::Control* GetRootControl() override;
+
+ String GetLabelText() const { return label_.GetText(); }
+ void SetLabelText(String label) { label_.SetText(std::move(label)); }
+
+ std::shared_ptr<platform::graphics::IFont> GetValue() const;
+ void SetValue(std::shared_ptr<platform::graphics::IFont> value,
+ bool trigger_change = true);
+
+ private:
+ ui::controls::FlexLayout main_container_;
+ ui::controls::TextBlock label_;
+ ui::controls::FlexLayout right_container_;
+ ui::controls::FlexLayout font_family_container_;
+ ui::controls::TextBlock font_family_label_;
+ ui::controls::TextBox font_family_text_;
+ ui::controls::FlexLayout font_size_container_;
+ ui::controls::TextBlock font_size_label_;
+ ui::components::FloatInput font_size_input_;
+};
+} // namespace cru::theme_builder::components::properties
diff --git a/src/ThemeBuilder/components/properties/MeasureLengthPropertyEditor.cpp b/src/ThemeBuilder/components/properties/MeasureLengthPropertyEditor.cpp
new file mode 100644
index 00000000..d1f4afce
--- /dev/null
+++ b/src/ThemeBuilder/components/properties/MeasureLengthPropertyEditor.cpp
@@ -0,0 +1,37 @@
+#include "MeasureLengthPropertyEditor.h"
+#include "cru/common/Format.h"
+#include "cru/ui/mapper/MapperRegistry.h"
+#include "cru/ui/render/MeasureRequirement.h"
+
+namespace cru::theme_builder::components::properties {
+MeasureLengthPropertyEditor::MeasureLengthPropertyEditor() {
+ container_.AddChild(&label_);
+ container_.AddChild(&text_);
+
+ text_.TextChangeEvent()->AddHandler([this](std::nullptr_t) {
+ auto text = text_.GetTextView();
+ auto measure_length_mapper = ui::mapper::MapperRegistry::GetInstance()
+ ->GetMapper<ui::render::MeasureLength>();
+ try {
+ auto measure_length =
+ measure_length_mapper->MapFromString(text.ToString());
+ measure_length_ = measure_length;
+ is_text_valid_ = true;
+ RaiseChangeEvent();
+ } catch (const Exception&) {
+ is_text_valid_ = false;
+ // TODO: Show error!
+ }
+ });
+}
+
+MeasureLengthPropertyEditor::~MeasureLengthPropertyEditor() {}
+
+void MeasureLengthPropertyEditor::SetValue(
+ const ui::render::MeasureLength& value, bool trigger_change) {
+ if (!trigger_change) SuppressNextChangeEvent();
+ text_.SetText(measure_length_.IsNotSpecified()
+ ? u"unspecified"
+ : ToString(measure_length_.GetLengthOrUndefined()));
+}
+} // namespace cru::theme_builder::components::properties
diff --git a/src/ThemeBuilder/components/properties/MeasureLengthPropertyEditor.h b/src/ThemeBuilder/components/properties/MeasureLengthPropertyEditor.h
new file mode 100644
index 00000000..43e783c5
--- /dev/null
+++ b/src/ThemeBuilder/components/properties/MeasureLengthPropertyEditor.h
@@ -0,0 +1,35 @@
+#pragma once
+#include "../Editor.h"
+#include "cru/platform/graphics/Base.h"
+#include "cru/ui/controls/Container.h"
+#include "cru/ui/controls/FlexLayout.h"
+#include "cru/ui/controls/TextBlock.h"
+#include "cru/ui/controls/TextBox.h"
+#include "cru/ui/render/MeasureRequirement.h"
+
+namespace cru::theme_builder::components::properties {
+class MeasureLengthPropertyEditor : public Editor {
+ public:
+ using PropertyType = ui::render::MeasureLength;
+
+ MeasureLengthPropertyEditor();
+ ~MeasureLengthPropertyEditor() override;
+
+ public:
+ ui::controls::Control* GetRootControl() override { return &container_; }
+
+ String GetLabel() const { return label_.GetText(); }
+ void SetLabel(String label) { label_.SetText(std::move(label)); }
+
+ PropertyType GetValue() const { return measure_length_; }
+ void SetValue(const PropertyType& value, bool trigger_change = true);
+
+ private:
+ PropertyType measure_length_;
+
+ ui::controls::FlexLayout container_;
+ ui::controls::TextBlock label_;
+ ui::controls::TextBox text_;
+ bool is_text_valid_;
+};
+} // namespace cru::theme_builder::components::properties
diff --git a/src/ThemeBuilder/components/properties/OptionalPropertyEditor.h b/src/ThemeBuilder/components/properties/OptionalPropertyEditor.h
new file mode 100644
index 00000000..0f22616a
--- /dev/null
+++ b/src/ThemeBuilder/components/properties/OptionalPropertyEditor.h
@@ -0,0 +1,65 @@
+#pragma once
+#include "../Editor.h"
+#include "cru/ui/controls/CheckBox.h"
+#include "cru/ui/controls/FlexLayout.h"
+#include "cru/ui/controls/TextBlock.h"
+
+#include <optional>
+
+namespace cru::theme_builder::components::properties {
+template <typename TEditor>
+class OptionalPropertyEditor : public Editor {
+ public:
+ using PropertyType = typename TEditor::PropertyType;
+
+ OptionalPropertyEditor() {
+ container_.AddChild(&label_);
+ container_.AddChild(&check_box_);
+ check_box_.SetMargin({0, 0, 10, 0});
+ container_.AddChild(editor_.GetRootControl());
+
+ editor_.ChangeEvent()->AddHandler([this](std::nullptr_t) {
+ if (IsEnabled()) {
+ RaiseChangeEvent();
+ }
+ });
+ }
+ ~OptionalPropertyEditor() override {}
+
+ ui::controls::Control* GetRootControl() override { return &container_; }
+
+ String GetLabel() const { return label_.GetText(); }
+ void SetLabel(String label) { label_.SetText(std::move(label)); }
+
+ bool IsEnabled() const { return check_box_.IsChecked(); }
+ void SetEnabled(bool enabled, bool trigger_change = true) {
+ check_box_.SetChecked(enabled);
+ if (trigger_change) {
+ RaiseChangeEvent();
+ }
+ }
+
+ std::optional<PropertyType> GetValue() const {
+ return IsEnabled() ? std::optional<PropertyType>(editor_.GetValue())
+ : std::nullopt;
+ }
+
+ void SetValue(std::optional<PropertyType> value, bool trigger_change = true) {
+ if (value) {
+ SetEnabled(true, false);
+ editor_.SetValue(*value, false);
+ if (trigger_change) RaiseChangeEvent();
+ } else {
+ SetEnabled(false, trigger_change);
+ }
+ }
+
+ TEditor* GetEditor() { return &editor_; }
+
+ private:
+ ui::controls::FlexLayout container_;
+ ui::controls::TextBlock label_;
+ ui::controls::CheckBox check_box_;
+ TEditor editor_;
+};
+} // namespace cru::theme_builder::components::properties
diff --git a/src/ThemeBuilder/components/properties/PointPropertyEditor.cpp b/src/ThemeBuilder/components/properties/PointPropertyEditor.cpp
new file mode 100644
index 00000000..6d4277aa
--- /dev/null
+++ b/src/ThemeBuilder/components/properties/PointPropertyEditor.cpp
@@ -0,0 +1,38 @@
+#include "PointPropertyEditor.h"
+#include "cru/common/Format.h"
+#include "cru/ui/mapper/MapperRegistry.h"
+#include "cru/ui/mapper/PointMapper.h"
+
+namespace cru::theme_builder::components::properties {
+PointPropertyEditor::PointPropertyEditor() {
+ container_.AddChild(&label_);
+ container_.AddChild(&text_);
+
+ text_.TextChangeEvent()->AddHandler([this](std::nullptr_t) {
+ auto text = text_.GetTextView();
+ auto point_mapper =
+ ui::mapper::MapperRegistry::GetInstance()->GetMapper<ui::Point>();
+ try {
+ auto point = point_mapper->MapFromString(text.ToString());
+ point_ = point;
+ is_text_valid_ = true;
+ RaiseChangeEvent();
+ } catch (const Exception&) {
+ is_text_valid_ = false;
+ // TODO: Show error!
+ }
+ });
+}
+
+PointPropertyEditor::~PointPropertyEditor() {}
+
+void PointPropertyEditor::SetValue(const ui::Point& point,
+ bool trigger_change) {
+ if (!trigger_change) SuppressNextChangeEvent();
+ text_.SetText(ConvertPointToString(point));
+}
+
+String PointPropertyEditor::ConvertPointToString(const ui::Point& point) {
+ return Format(u"{} {}", point.x, point.y);
+}
+} // namespace cru::theme_builder::components::properties
diff --git a/src/ThemeBuilder/components/properties/PointPropertyEditor.h b/src/ThemeBuilder/components/properties/PointPropertyEditor.h
new file mode 100644
index 00000000..bd852e3a
--- /dev/null
+++ b/src/ThemeBuilder/components/properties/PointPropertyEditor.h
@@ -0,0 +1,35 @@
+#pragma once
+#include "../Editor.h"
+#include "cru/ui/controls/FlexLayout.h"
+#include "cru/ui/controls/TextBlock.h"
+#include "cru/ui/controls/TextBox.h"
+
+namespace cru::theme_builder::components::properties {
+class PointPropertyEditor : public Editor {
+ public:
+ using PropertyType = ui::Point;
+
+ PointPropertyEditor();
+ ~PointPropertyEditor() override;
+
+ public:
+ ui::controls::Control* GetRootControl() override { return &container_; }
+
+ String GetLabel() const { return label_.GetText(); }
+ void SetLabel(String label) { label_.SetText(std::move(label)); }
+
+ ui::Point GetValue() const { return point_; }
+ void SetValue(const ui::Point& point, bool trigger_change = true);
+
+ private:
+ static String ConvertPointToString(const ui::Point& point);
+
+ private:
+ ui::Point point_;
+
+ ui::controls::FlexLayout container_;
+ ui::controls::TextBlock label_;
+ ui::controls::TextBox text_;
+ bool is_text_valid_;
+};
+} // namespace cru::theme_builder::components::properties
diff --git a/src/ThemeBuilder/components/properties/SelectPropertyEditor.cpp b/src/ThemeBuilder/components/properties/SelectPropertyEditor.cpp
new file mode 100644
index 00000000..835b2d12
--- /dev/null
+++ b/src/ThemeBuilder/components/properties/SelectPropertyEditor.cpp
@@ -0,0 +1,15 @@
+#include "SelectPropertyEditor.h"
+#include "cru/ui/controls/FlexLayout.h"
+
+namespace cru::theme_builder::components::properties {
+SelectPropertyEditor::SelectPropertyEditor() {
+ container_.SetFlexDirection(ui::controls::FlexDirection::Horizontal);
+ container_.AddChild(&label_);
+ container_.AddChild(select_.GetRootControl());
+
+ select_.ItemSelectedEvent()->AddHandler(
+ [this](Index index) { RaiseChangeEvent(); });
+}
+
+SelectPropertyEditor::~SelectPropertyEditor() {}
+} // namespace cru::theme_builder::components::properties
diff --git a/src/ThemeBuilder/components/properties/SelectPropertyEditor.h b/src/ThemeBuilder/components/properties/SelectPropertyEditor.h
new file mode 100644
index 00000000..475d2d0a
--- /dev/null
+++ b/src/ThemeBuilder/components/properties/SelectPropertyEditor.h
@@ -0,0 +1,42 @@
+#pragma once
+#include "../Editor.h"
+#include "cru/ui/components/Select.h"
+#include "cru/ui/controls/FlexLayout.h"
+#include "cru/ui/controls/TextBlock.h"
+
+namespace cru::theme_builder::components::properties {
+class SelectPropertyEditor : public Editor {
+ public:
+ using PropertyType = Index;
+
+ SelectPropertyEditor();
+ ~SelectPropertyEditor() override;
+
+ public:
+ ui::controls::Control* GetRootControl() override { return &container_; }
+
+ String GetLabel() const { return label_.GetText(); }
+ void SetLabel(String label) { label_.SetText(std::move(label)); }
+
+ Index GetSelectedIndex() const { return select_.GetSelectedIndex(); }
+ void SetSelectedIndex(Index index, bool trigger_change = true) {
+ if (trigger_change == false) SuppressNextChangeEvent();
+ select_.SetSelectedIndex(index);
+ }
+
+ std::vector<String> GetItems() const { return select_.GetItems(); }
+ void SetItems(std::vector<String> items) {
+ select_.SetItems(std::move(items));
+ }
+
+ Index GetValue() const { return GetSelectedIndex(); }
+ void SetValue(Index value, bool trigger_change = true) {
+ SetSelectedIndex(value, trigger_change);
+ }
+
+ private:
+ ui::controls::FlexLayout container_;
+ ui::controls::TextBlock label_;
+ ui::components::Select select_;
+};
+} // namespace cru::theme_builder::components::properties
diff --git a/src/ThemeBuilder/components/properties/TextPropertyEditor.cpp b/src/ThemeBuilder/components/properties/TextPropertyEditor.cpp
new file mode 100644
index 00000000..9854019c
--- /dev/null
+++ b/src/ThemeBuilder/components/properties/TextPropertyEditor.cpp
@@ -0,0 +1,22 @@
+#include "TextPropertyEditor.h"
+
+namespace cru::theme_builder::components::properties {
+TextPropertyEditor::TextPropertyEditor() {
+ editor_.TextChangeEvent()->AddHandler([this](std::nullptr_t) {
+ auto text_view = editor_.GetTextView();
+ String error_message;
+ auto validation_result = Validate(text_view, &error_message);
+ if (validation_result) {
+ OnTextChanged(text_view);
+ }
+ });
+}
+
+TextPropertyEditor::~TextPropertyEditor() {}
+
+bool TextPropertyEditor::Validate(StringView text, String* error_message) {
+ return true;
+}
+
+void TextPropertyEditor::OnTextChanged(StringView text) {}
+} // namespace cru::theme_builder::components::properties
diff --git a/src/ThemeBuilder/components/properties/TextPropertyEditor.h b/src/ThemeBuilder/components/properties/TextPropertyEditor.h
new file mode 100644
index 00000000..c4944228
--- /dev/null
+++ b/src/ThemeBuilder/components/properties/TextPropertyEditor.h
@@ -0,0 +1,31 @@
+#pragma once
+#include "cru/ui/components/Component.h"
+#include "cru/ui/controls/FlexLayout.h"
+#include "cru/ui/controls/TextBlock.h"
+#include "cru/ui/controls/TextBox.h"
+
+namespace cru::theme_builder::components::properties {
+class TextPropertyEditor : public ui::components::Component {
+ public:
+ TextPropertyEditor();
+ ~TextPropertyEditor() override;
+
+ ui::controls::Control* GetRootControl() override { return &container_; }
+
+ String GetLabel() const { return label_.GetText(); }
+ void SetLabel(String label) { label_.SetText(std::move(label)); }
+
+ String GetText() const { return editor_.GetText(); }
+ StringView GetTextView() const { return editor_.GetTextView(); }
+ void SetText(String text) { editor_.SetText(std::move(text)); }
+
+ protected:
+ virtual bool Validate(StringView text, String* error_message);
+ virtual void OnTextChanged(StringView text);
+
+ private:
+ ui::controls::FlexLayout container_;
+ ui::controls::TextBlock label_;
+ ui::controls::TextBox editor_;
+};
+} // namespace cru::theme_builder::components::properties
diff --git a/src/ThemeBuilder/components/properties/ThicknessPropertyEditor.cpp b/src/ThemeBuilder/components/properties/ThicknessPropertyEditor.cpp
new file mode 100644
index 00000000..3e022bb1
--- /dev/null
+++ b/src/ThemeBuilder/components/properties/ThicknessPropertyEditor.cpp
@@ -0,0 +1,34 @@
+#include "ThicknessPropertyEditor.h"
+#include "cru/ui/mapper/MapperRegistry.h"
+#include "cru/ui/mapper/ThicknessMapper.h"
+
+namespace cru::theme_builder::components::properties {
+ThicknessPropertyEditor::ThicknessPropertyEditor() {
+ container_.AddChild(&label_);
+ container_.AddChild(&text_);
+
+ text_.TextChangeEvent()->AddHandler([this](std::nullptr_t) {
+ auto text = text_.GetText();
+ auto thickness_mapper =
+ ui::mapper::MapperRegistry::GetInstance()->GetMapper<ui::Thickness>();
+ try {
+ auto thickness = thickness_mapper->MapFromString(text);
+ thickness_ = thickness;
+ is_text_valid_ = true;
+ RaiseChangeEvent();
+ } catch (const Exception &) {
+ is_text_valid_ = false;
+ // TODO: Show error!
+ }
+ });
+}
+
+ThicknessPropertyEditor::~ThicknessPropertyEditor() {}
+
+void ThicknessPropertyEditor::SetValue(const ui::Thickness &thickness,
+ bool trigger_change) {
+ if (!trigger_change) SuppressNextChangeEvent();
+ text_.SetText(Format(u"{} {} {} {}", thickness.left, thickness.top,
+ thickness.right, thickness.bottom));
+}
+} // namespace cru::theme_builder::components::properties
diff --git a/src/ThemeBuilder/components/properties/ThicknessPropertyEditor.h b/src/ThemeBuilder/components/properties/ThicknessPropertyEditor.h
new file mode 100644
index 00000000..cea9ae9d
--- /dev/null
+++ b/src/ThemeBuilder/components/properties/ThicknessPropertyEditor.h
@@ -0,0 +1,31 @@
+#pragma once
+#include "../Editor.h"
+#include "cru/ui/controls/FlexLayout.h"
+#include "cru/ui/controls/TextBlock.h"
+#include "cru/ui/controls/TextBox.h"
+
+namespace cru::theme_builder::components::properties {
+class ThicknessPropertyEditor : public Editor {
+ public:
+ using PropertyType = ui::Thickness;
+
+ ThicknessPropertyEditor();
+ ~ThicknessPropertyEditor() override;
+
+ ui::controls::Control* GetRootControl() override { return &container_; }
+
+ String GetLabel() const { return label_.GetText(); }
+ void SetLabel(String label) { label_.SetText(std::move(label)); }
+
+ ui::Thickness GetValue() const { return thickness_; }
+ void SetValue(const ui::Thickness& thickness, bool trigger_change = true);
+
+ private:
+ ui::Thickness thickness_;
+
+ ui::controls::FlexLayout container_;
+ ui::controls::TextBlock label_;
+ ui::controls::TextBox text_;
+ bool is_text_valid_;
+};
+} // namespace cru::theme_builder::components::properties
diff --git a/src/ThemeBuilder/components/stylers/BorderStylerEditor.cpp b/src/ThemeBuilder/components/stylers/BorderStylerEditor.cpp
new file mode 100644
index 00000000..81eb66d3
--- /dev/null
+++ b/src/ThemeBuilder/components/stylers/BorderStylerEditor.cpp
@@ -0,0 +1,100 @@
+#include "BorderStylerEditor.h"
+#include "cru/common/ClonablePtr.h"
+#include "cru/platform/graphics/Brush.h"
+#include "cru/platform/graphics/Factory.h"
+#include "cru/platform/gui/UiApplication.h"
+#include "cru/ui/style/ApplyBorderStyleInfo.h"
+#include "cru/ui/style/Styler.h"
+
+namespace cru::theme_builder::components::stylers {
+BorderStylerEditor::BorderStylerEditor() {
+ SetLabel(u"Border Styler");
+ GetContainer()->AddChild(corner_radius_editor_.GetRootControl());
+ GetContainer()->AddChild(thickness_editor_.GetRootControl());
+ GetContainer()->AddChild(brush_editor_.GetRootControl());
+ GetContainer()->AddChild(foreground_brush_editor_.GetRootControl());
+ GetContainer()->AddChild(background_brush_editor_.GetRootControl());
+
+ thickness_editor_.GetEditor()->SetLabel(u"Thickness");
+ brush_editor_.GetEditor()->SetLabel(u"Border");
+ foreground_brush_editor_.GetEditor()->SetLabel(u"Foreground");
+ background_brush_editor_.GetEditor()->SetLabel(u"Background");
+
+ ConnectChangeEvent(corner_radius_editor_);
+ ConnectChangeEvent(thickness_editor_);
+ ConnectChangeEvent(brush_editor_);
+ ConnectChangeEvent(foreground_brush_editor_);
+ ConnectChangeEvent(background_brush_editor_);
+}
+
+BorderStylerEditor::~BorderStylerEditor() {}
+
+ClonablePtr<ui::style::BorderStyler> BorderStylerEditor::GetValue() {
+ auto graphics_factory =
+ platform::gui::IUiApplication::GetInstance()->GetGraphicsFactory();
+
+ ui::style::ApplyBorderStyleInfo border_style;
+ border_style.border_radius = corner_radius_editor_.GetValue();
+ border_style.border_thickness = thickness_editor_.GetValue();
+
+ if (brush_editor_.IsEnabled()) {
+ border_style.border_brush = graphics_factory->CreateSolidColorBrush(
+ brush_editor_.GetEditor()->GetValue());
+ }
+
+ if (foreground_brush_editor_.IsEnabled()) {
+ border_style.foreground_brush = graphics_factory->CreateSolidColorBrush(
+ foreground_brush_editor_.GetEditor()->GetValue());
+ }
+
+ if (background_brush_editor_.IsEnabled()) {
+ border_style.background_brush = graphics_factory->CreateSolidColorBrush(
+ background_brush_editor_.GetEditor()->GetValue());
+ }
+
+ return ui::style::BorderStyler::Create(border_style);
+}
+
+void BorderStylerEditor::SetValue(ui::style::BorderStyler* styler,
+ bool trigger_change) {
+ Expects(styler);
+
+ auto border_style = styler->GetBorderStyle();
+ corner_radius_editor_.SetValue(border_style.border_radius, false);
+ thickness_editor_.SetValue(border_style.border_thickness, false);
+
+ brush_editor_.SetEnabled(border_style.border_brush.has_value(), false);
+ if (border_style.border_brush.has_value()) {
+ brush_editor_.GetEditor()->SetValue(
+ std::dynamic_pointer_cast<platform::graphics::ISolidColorBrush>(
+ border_style.border_brush.value())
+ ->GetColor(),
+ false);
+ }
+
+ foreground_brush_editor_.SetEnabled(border_style.foreground_brush.has_value(),
+ false);
+ if (border_style.foreground_brush.has_value()) {
+ foreground_brush_editor_.GetEditor()->SetValue(
+ std::dynamic_pointer_cast<platform::graphics::ISolidColorBrush>(
+ border_style.foreground_brush.value())
+ ->GetColor(),
+ false);
+ }
+
+ background_brush_editor_.SetEnabled(border_style.background_brush.has_value(),
+ false);
+ if (border_style.background_brush.has_value()) {
+ background_brush_editor_.GetEditor()->SetValue(
+ std::dynamic_pointer_cast<platform::graphics::ISolidColorBrush>(
+ border_style.background_brush.value())
+ ->GetColor(),
+ false);
+ }
+
+ if (trigger_change) {
+ RaiseChangeEvent();
+ }
+}
+
+} // namespace cru::theme_builder::components::stylers
diff --git a/src/ThemeBuilder/components/stylers/BorderStylerEditor.h b/src/ThemeBuilder/components/stylers/BorderStylerEditor.h
new file mode 100644
index 00000000..539262d6
--- /dev/null
+++ b/src/ThemeBuilder/components/stylers/BorderStylerEditor.h
@@ -0,0 +1,36 @@
+#pragma once
+#include "../properties/ColorPropertyEditor.h"
+#include "../properties/CornerRadiusPropertyEditor.h"
+#include "../properties/OptionalPropertyEditor.h"
+#include "../properties/ThicknessPropertyEditor.h"
+#include "StylerEditor.h"
+#include "cru/common/ClonablePtr.h"
+
+namespace cru::theme_builder::components::stylers {
+class BorderStylerEditor : public StylerEditor {
+ public:
+ BorderStylerEditor();
+ ~BorderStylerEditor() override;
+
+ ClonablePtr<ui::style::BorderStyler> GetValue();
+ void SetValue(ui::style::BorderStyler* styler, bool trigger_change = true);
+ void SetValue(const ClonablePtr<ui::style::BorderStyler>& styler,
+ bool trigger_change = true) {
+ SetValue(styler.get(), trigger_change);
+ }
+
+ ClonablePtr<ui::style::Styler> GetStyler() override { return GetValue(); }
+
+ private:
+ properties::OptionalPropertyEditor<properties::CornerRadiusPropertyEditor>
+ corner_radius_editor_;
+ properties::OptionalPropertyEditor<properties::ThicknessPropertyEditor>
+ thickness_editor_;
+ properties::OptionalPropertyEditor<properties::ColorPropertyEditor>
+ brush_editor_;
+ properties::OptionalPropertyEditor<properties::ColorPropertyEditor>
+ foreground_brush_editor_;
+ properties::OptionalPropertyEditor<properties::ColorPropertyEditor>
+ background_brush_editor_;
+};
+} // namespace cru::theme_builder::components::stylers
diff --git a/src/ThemeBuilder/components/stylers/CompoundStylerEditor.cpp b/src/ThemeBuilder/components/stylers/CompoundStylerEditor.cpp
new file mode 100644
index 00000000..6b8a5033
--- /dev/null
+++ b/src/ThemeBuilder/components/stylers/CompoundStylerEditor.cpp
@@ -0,0 +1,105 @@
+#include "CompoundStylerEditor.h"
+#include "BorderStylerEditor.h"
+#include "ContentBrushStylerEditor.h"
+#include "CursorStylerEditor.h"
+#include "FontStylerEditor.h"
+#include "MarginStylerEditor.h"
+#include "PaddingStylerEditor.h"
+#include "PreferredSizeStylerEditor.h"
+#include "cru/common/ClonablePtr.h"
+#include "cru/ui/ThemeManager.h"
+#include "cru/ui/style/Styler.h"
+
+namespace cru::theme_builder::components::stylers {
+CompoundStylerEditor::CompoundStylerEditor() {
+ SetLabel(u"Compound Styler");
+ GetContainer()->AddChild(&children_container_);
+ children_container_.SetFlexDirection(ui::controls::FlexDirection::Vertical);
+ children_container_.SetItemCrossAlign(
+ ui::controls::FlexCrossAlignment::Start);
+
+ GetHeadContainer()->AddChild(add_child_button_.GetRootControl());
+ add_child_button_.GetButton()->GetStyleRuleSet()->SetParent(
+ ui::ThemeManager::GetInstance()->GetResourceStyleRuleSet(
+ u"cru.theme_builder.icon-button.style"));
+ add_child_button_.GetButton()->SetIconWithSvgPathDataStringResourceKey(
+ u"icon.plus", {0, 0, 16, 16});
+ add_child_button_.GetButton()->SetPreferredSize({24, 24});
+ add_child_button_.GetButton()->SetPadding(ui::Thickness(2));
+ add_child_button_.GetButton()->SetIconFillColor(ui::colors::green);
+ add_child_button_.SetMenuItems({u"Compound Styler", u"Border Styler",
+ u"Cursor Styler", u"Content Brush Styler",
+ u"Font Styler", u"Margin Styler",
+ u"Padding Styler", u"Preferred Size Styler"});
+ add_child_button_.MenuItemSelectedEvent()->AddHandler([this](Index index) {
+ std::unique_ptr<StylerEditor> editor;
+ switch (index) {
+ case 0:
+ editor = std::make_unique<CompoundStylerEditor>();
+ break;
+ case 1:
+ editor = std::make_unique<BorderStylerEditor>();
+ break;
+ case 2:
+ editor = std::make_unique<CursorStylerEditor>();
+ break;
+ case 3:
+ editor = std::make_unique<ContentBrushStylerEditor>();
+ break;
+ case 4:
+ editor = std::make_unique<FontStylerEditor>();
+ break;
+ case 5:
+ editor = std::make_unique<MarginStylerEditor>();
+ break;
+ case 6:
+ editor = std::make_unique<PaddingStylerEditor>();
+ break;
+ case 7:
+ editor = std::make_unique<PreferredSizeStylerEditor>();
+ break;
+ default:
+ break;
+ }
+ if (editor) {
+ ConnectChangeEvent(editor.get());
+ editor->RemoveEvent()->AddSpyOnlyHandler([this, c = editor.get()] {
+ auto index = this->children_container_.IndexOf(c->GetRootControl());
+ this->children_.erase(this->children_.begin() + index);
+ this->children_container_.RemoveChildAt(index);
+ RaiseChangeEvent();
+ });
+ children_.push_back(std::move(editor));
+ children_container_.AddChild(editor->GetRootControl());
+ RaiseChangeEvent();
+ }
+ });
+}
+
+CompoundStylerEditor::~CompoundStylerEditor() {}
+
+ClonablePtr<ui::style::CompoundStyler> CompoundStylerEditor::GetValue() {
+ std::vector<ClonablePtr<ui::style::Styler>> children_styler;
+ for (auto& child : children_) {
+ children_styler.push_back(child->GetStyler());
+ }
+ return ui::style::CompoundStyler::Create(std::move(children_styler));
+}
+
+void CompoundStylerEditor::SetValue(ui::style::CompoundStyler* value,
+ bool trigger_change) {
+ children_.clear();
+ for (const auto& styler : value->GetChildren()) {
+ auto editor = CreateStylerEditor(styler.get());
+ ConnectChangeEvent(editor.get());
+ editor->RemoveEvent()->AddSpyOnlyHandler([this, c = editor.get()] {
+ auto index = this->children_container_.IndexOf(c->GetRootControl());
+ this->children_.erase(this->children_.begin() + index);
+ this->children_container_.RemoveChildAt(index);
+ RaiseChangeEvent();
+ });
+ children_.push_back(std::move(editor));
+ children_container_.AddChild(children_.back()->GetRootControl());
+ }
+}
+} // namespace cru::theme_builder::components::stylers
diff --git a/src/ThemeBuilder/components/stylers/CompoundStylerEditor.h b/src/ThemeBuilder/components/stylers/CompoundStylerEditor.h
new file mode 100644
index 00000000..57150e83
--- /dev/null
+++ b/src/ThemeBuilder/components/stylers/CompoundStylerEditor.h
@@ -0,0 +1,30 @@
+#pragma once
+#include "StylerEditor.h"
+#include "cru/common/ClonablePtr.h"
+#include "cru/ui/DeleteLater.h"
+#include "cru/ui/components/PopupButton.h"
+#include "cru/ui/controls/FlexLayout.h"
+#include "cru/ui/style/Styler.h"
+
+namespace cru::theme_builder::components::stylers {
+class CompoundStylerEditor : public StylerEditor {
+ public:
+ CompoundStylerEditor();
+ ~CompoundStylerEditor() override;
+
+ public:
+ ClonablePtr<ui::style::CompoundStyler> GetValue();
+ void SetValue(ui::style::CompoundStyler* styler, bool trigger_change = true);
+ void SetValue(const ClonablePtr<ui::style::CompoundStyler>& styler,
+ bool trigger_change = true) {
+ SetValue(styler.get(), trigger_change);
+ }
+
+ ClonablePtr<ui::style::Styler> GetStyler() override { return GetValue(); }
+
+ private:
+ ui::controls::FlexLayout children_container_;
+ std::vector<ui::DeleteLaterPtr<StylerEditor>> children_;
+ ui::components::PopupMenuIconButton add_child_button_;
+};
+} // namespace cru::theme_builder::components::stylers
diff --git a/src/ThemeBuilder/components/stylers/ContentBrushStylerEditor.cpp b/src/ThemeBuilder/components/stylers/ContentBrushStylerEditor.cpp
new file mode 100644
index 00000000..bd649c67
--- /dev/null
+++ b/src/ThemeBuilder/components/stylers/ContentBrushStylerEditor.cpp
@@ -0,0 +1,31 @@
+#include "ContentBrushStylerEditor.h"
+#include "cru/platform/graphics/Factory.h"
+#include "cru/platform/gui/UiApplication.h"
+#include "cru/ui/style/Styler.h"
+
+namespace cru::theme_builder::components::stylers {
+ContentBrushStylerEditor::ContentBrushStylerEditor() {
+ GetContainer()->AddChild(color_editor_.GetRootControl());
+
+ ConnectChangeEvent(color_editor_);
+}
+
+ContentBrushStylerEditor::~ContentBrushStylerEditor() {}
+
+ClonablePtr<ui::style::ContentBrushStyler> ContentBrushStylerEditor::GetValue()
+ const {
+ return ui::style::ContentBrushStyler::Create(
+ platform::gui::IUiApplication::GetInstance()
+ ->GetGraphicsFactory()
+ ->CreateSolidColorBrush(color_editor_.GetValue()));
+}
+
+void ContentBrushStylerEditor::SetValue(ui::style::ContentBrushStyler* value,
+ bool trigger_change) {
+ color_editor_.SetValue(
+ std::dynamic_pointer_cast<platform::graphics::ISolidColorBrush>(
+ value->GetBrush())
+ ->GetColor(),
+ trigger_change);
+}
+} // namespace cru::theme_builder::components::stylers
diff --git a/src/ThemeBuilder/components/stylers/ContentBrushStylerEditor.h b/src/ThemeBuilder/components/stylers/ContentBrushStylerEditor.h
new file mode 100644
index 00000000..8385b7c3
--- /dev/null
+++ b/src/ThemeBuilder/components/stylers/ContentBrushStylerEditor.h
@@ -0,0 +1,24 @@
+#pragma once
+#include "../Editor.h"
+#include "../properties/ColorPropertyEditor.h"
+#include "StylerEditor.h"
+#include "cru/common/ClonablePtr.h"
+#include "cru/ui/style/Styler.h"
+
+namespace cru::theme_builder::components::stylers {
+class ContentBrushStylerEditor : public StylerEditor {
+ public:
+ ContentBrushStylerEditor();
+ ~ContentBrushStylerEditor();
+
+ public:
+ ClonablePtr<ui::style::ContentBrushStyler> GetValue() const;
+ void SetValue(ui::style::ContentBrushStyler* value,
+ bool trigger_change = true);
+
+ ClonablePtr<ui::style::Styler> GetStyler() override { return GetValue(); }
+
+ private:
+ properties::ColorPropertyEditor color_editor_;
+};
+} // namespace cru::theme_builder::components::stylers
diff --git a/src/ThemeBuilder/components/stylers/CursorStylerEditor.cpp b/src/ThemeBuilder/components/stylers/CursorStylerEditor.cpp
new file mode 100644
index 00000000..9984d81a
--- /dev/null
+++ b/src/ThemeBuilder/components/stylers/CursorStylerEditor.cpp
@@ -0,0 +1,65 @@
+#include "CursorStylerEditor.h"
+#include "cru/platform/gui/Cursor.h"
+#include "cru/platform/gui/UiApplication.h"
+
+namespace cru::theme_builder::components::stylers {
+CursorStylerEditor::CursorStylerEditor() {
+ SetLabel(u"Cursor Styler");
+ GetContainer()->AddChild(cursor_select_.GetRootControl());
+
+ cursor_select_.SetLabel(u"Cursor");
+ cursor_select_.SetItems({u"arrow", u"hand", u"ibeam"});
+ cursor_select_.SetSelectedIndex(0);
+
+ ConnectChangeEvent(cursor_select_);
+}
+
+CursorStylerEditor::~CursorStylerEditor() {}
+
+ClonablePtr<ui::style::CursorStyler> CursorStylerEditor::GetValue() {
+ auto cursor_manager =
+ platform::gui::IUiApplication::GetInstance()->GetCursorManager();
+
+ std::shared_ptr<platform::gui::ICursor> cursor;
+
+ switch (cursor_select_.GetSelectedIndex()) {
+ case 0:
+ cursor = cursor_manager->GetSystemCursor(
+ platform::gui::SystemCursorType::Arrow);
+ break;
+ case 1:
+ cursor = cursor_manager->GetSystemCursor(
+ platform::gui::SystemCursorType::Hand);
+ break;
+ case 2:
+ cursor = cursor_manager->GetSystemCursor(
+ platform::gui::SystemCursorType::IBeam);
+ break;
+ }
+
+ return ui::style::CursorStyler::Create(cursor);
+}
+
+void CursorStylerEditor::SetValue(ui::style::CursorStyler* styler,
+ bool trigger_change) {
+ auto cursor_manager =
+ platform::gui::IUiApplication::GetInstance()->GetCursorManager();
+
+ auto cursor = styler->GetCursor();
+
+ if (cursor ==
+ cursor_manager->GetSystemCursor(platform::gui::SystemCursorType::Arrow)) {
+ cursor_select_.SetSelectedIndex(0);
+ } else if (cursor == cursor_manager->GetSystemCursor(
+ platform::gui::SystemCursorType::Hand)) {
+ cursor_select_.SetSelectedIndex(1);
+ } else if (cursor == cursor_manager->GetSystemCursor(
+ platform::gui::SystemCursorType::IBeam)) {
+ cursor_select_.SetSelectedIndex(2);
+ }
+
+ if (trigger_change) {
+ RaiseChangeEvent();
+ }
+}
+} // namespace cru::theme_builder::components::stylers
diff --git a/src/ThemeBuilder/components/stylers/CursorStylerEditor.h b/src/ThemeBuilder/components/stylers/CursorStylerEditor.h
new file mode 100644
index 00000000..552619a0
--- /dev/null
+++ b/src/ThemeBuilder/components/stylers/CursorStylerEditor.h
@@ -0,0 +1,24 @@
+#pragma once
+#include "../properties/SelectPropertyEditor.h"
+#include "StylerEditor.h"
+
+namespace cru::theme_builder::components::stylers {
+class CursorStylerEditor : public StylerEditor {
+ public:
+ CursorStylerEditor();
+ ~CursorStylerEditor() override;
+
+ public:
+ ClonablePtr<ui::style::CursorStyler> GetValue();
+ void SetValue(ui::style::CursorStyler* styler, bool trigger_change = true);
+ void SetValue(const ClonablePtr<ui::style::CursorStyler>& styler,
+ bool trigger_change = true) {
+ SetValue(styler.get(), trigger_change);
+ }
+
+ ClonablePtr<ui::style::Styler> GetStyler() override { return GetValue(); }
+
+ private:
+ properties::SelectPropertyEditor cursor_select_;
+};
+} // namespace cru::theme_builder::components::stylers
diff --git a/src/ThemeBuilder/components/stylers/FontStylerEditor.cpp b/src/ThemeBuilder/components/stylers/FontStylerEditor.cpp
new file mode 100644
index 00000000..9787a4fd
--- /dev/null
+++ b/src/ThemeBuilder/components/stylers/FontStylerEditor.cpp
@@ -0,0 +1,23 @@
+#include "FontStylerEditor.h"
+#include "cru/platform/graphics/Factory.h"
+#include "cru/platform/gui/UiApplication.h"
+#include "cru/ui/style/Styler.h"
+
+namespace cru::theme_builder::components::stylers {
+FontStylerEditor::FontStylerEditor() {
+ GetContainer()->AddChild(font_editor_.GetRootControl());
+
+ ConnectChangeEvent(font_editor_);
+}
+
+FontStylerEditor::~FontStylerEditor() {}
+
+ClonablePtr<ui::style::FontStyler> FontStylerEditor::GetValue() const {
+ return ui::style::FontStyler::Create(font_editor_.GetValue());
+}
+
+void FontStylerEditor::SetValue(ui::style::FontStyler* value,
+ bool trigger_change) {
+ font_editor_.SetValue(value->GetFont(), trigger_change);
+}
+} // namespace cru::theme_builder::components::stylers
diff --git a/src/ThemeBuilder/components/stylers/FontStylerEditor.h b/src/ThemeBuilder/components/stylers/FontStylerEditor.h
new file mode 100644
index 00000000..ccd12113
--- /dev/null
+++ b/src/ThemeBuilder/components/stylers/FontStylerEditor.h
@@ -0,0 +1,23 @@
+#pragma once
+#include "../Editor.h"
+#include "../properties/FontPropertyEditor.h"
+#include "StylerEditor.h"
+#include "cru/common/ClonablePtr.h"
+#include "cru/ui/style/Styler.h"
+
+namespace cru::theme_builder::components::stylers {
+class FontStylerEditor : public StylerEditor {
+ public:
+ FontStylerEditor();
+ ~FontStylerEditor();
+
+ public:
+ ClonablePtr<ui::style::FontStyler> GetValue() const;
+ void SetValue(ui::style::FontStyler* value, bool trigger_change = true);
+
+ ClonablePtr<ui::style::Styler> GetStyler() override { return GetValue(); }
+
+ private:
+ properties::FontPropertyEditor font_editor_;
+};
+} // namespace cru::theme_builder::components::stylers
diff --git a/src/ThemeBuilder/components/stylers/MarginStylerEditor.cpp b/src/ThemeBuilder/components/stylers/MarginStylerEditor.cpp
new file mode 100644
index 00000000..d7d89acb
--- /dev/null
+++ b/src/ThemeBuilder/components/stylers/MarginStylerEditor.cpp
@@ -0,0 +1,28 @@
+#include "MarginStylerEditor.h"
+#include "cru/ui/style/Styler.h"
+
+namespace cru::theme_builder::components::stylers {
+MarginStylerEditor::MarginStylerEditor() {
+ SetLabel(u"Margin Styler");
+ GetContainer()->AddChild(thickness_editor_.GetRootControl());
+
+ thickness_editor_.SetLabel(u"Thickness");
+
+ ConnectChangeEvent(thickness_editor_);
+}
+
+MarginStylerEditor::~MarginStylerEditor() {}
+
+ClonablePtr<ui::style::MarginStyler> MarginStylerEditor::GetValue() {
+ return ui::style::MarginStyler::Create(thickness_editor_.GetValue());
+}
+
+void MarginStylerEditor::SetValue(ui::style::MarginStyler* styler,
+ bool trigger_change) {
+ thickness_editor_.SetValue(styler->GetMargin(), false);
+
+ if (trigger_change) {
+ RaiseChangeEvent();
+ }
+}
+} // namespace cru::theme_builder::components::stylers
diff --git a/src/ThemeBuilder/components/stylers/MarginStylerEditor.h b/src/ThemeBuilder/components/stylers/MarginStylerEditor.h
new file mode 100644
index 00000000..9de6f1a2
--- /dev/null
+++ b/src/ThemeBuilder/components/stylers/MarginStylerEditor.h
@@ -0,0 +1,25 @@
+#pragma once
+#include "../properties/ThicknessPropertyEditor.h"
+#include "StylerEditor.h"
+#include "cru/common/ClonablePtr.h"
+#include "cru/ui/style/Styler.h"
+
+namespace cru::theme_builder::components::stylers {
+class MarginStylerEditor : public StylerEditor {
+ public:
+ MarginStylerEditor();
+ ~MarginStylerEditor() override;
+
+ ClonablePtr<ui::style::MarginStyler> GetValue();
+ void SetValue(ui::style::MarginStyler* styler, bool trigger_change = true);
+ void SetValue(const ClonablePtr<ui::style::MarginStyler>& styler,
+ bool trigger_change = true) {
+ SetValue(styler.get(), trigger_change);
+ }
+
+ ClonablePtr<ui::style::Styler> GetStyler() override { return GetValue(); }
+
+ private:
+ properties::ThicknessPropertyEditor thickness_editor_;
+};
+} // namespace cru::theme_builder::components::stylers
diff --git a/src/ThemeBuilder/components/stylers/PaddingStylerEditor.cpp b/src/ThemeBuilder/components/stylers/PaddingStylerEditor.cpp
new file mode 100644
index 00000000..476d21f1
--- /dev/null
+++ b/src/ThemeBuilder/components/stylers/PaddingStylerEditor.cpp
@@ -0,0 +1,28 @@
+#include "PaddingStylerEditor.h"
+#include "cru/ui/style/Styler.h"
+
+namespace cru::theme_builder::components::stylers {
+PaddingStylerEditor::PaddingStylerEditor() {
+ SetLabel(u"Padding Styler");
+ GetContainer()->AddChild(thickness_editor_.GetRootControl());
+
+ thickness_editor_.SetLabel(u"Thickness");
+
+ ConnectChangeEvent(thickness_editor_);
+}
+
+PaddingStylerEditor::~PaddingStylerEditor() {}
+
+ClonablePtr<ui::style::PaddingStyler> PaddingStylerEditor::GetValue() {
+ return ui::style::PaddingStyler::Create(thickness_editor_.GetValue());
+}
+
+void PaddingStylerEditor::SetValue(ui::style::PaddingStyler* styler,
+ bool trigger_change) {
+ thickness_editor_.SetValue(styler->GetPadding(), false);
+
+ if (trigger_change) {
+ RaiseChangeEvent();
+ }
+}
+} // namespace cru::theme_builder::components::stylers
diff --git a/src/ThemeBuilder/components/stylers/PaddingStylerEditor.h b/src/ThemeBuilder/components/stylers/PaddingStylerEditor.h
new file mode 100644
index 00000000..b78d310b
--- /dev/null
+++ b/src/ThemeBuilder/components/stylers/PaddingStylerEditor.h
@@ -0,0 +1,25 @@
+#pragma once
+#include "../properties/ThicknessPropertyEditor.h"
+#include "StylerEditor.h"
+#include "cru/common/ClonablePtr.h"
+#include "cru/ui/style/Styler.h"
+
+namespace cru::theme_builder::components::stylers {
+class PaddingStylerEditor : public StylerEditor {
+ public:
+ PaddingStylerEditor();
+ ~PaddingStylerEditor() override;
+
+ ClonablePtr<ui::style::PaddingStyler> GetValue();
+ void SetValue(ui::style::PaddingStyler* styler, bool trigger_change = true);
+ void SetValue(const ClonablePtr<ui::style::PaddingStyler>& styler,
+ bool trigger_change = true) {
+ SetValue(styler.get(), trigger_change);
+ }
+
+ ClonablePtr<ui::style::Styler> GetStyler() override { return GetValue(); }
+
+ private:
+ properties::ThicknessPropertyEditor thickness_editor_;
+};
+} // namespace cru::theme_builder::components::stylers
diff --git a/src/ThemeBuilder/components/stylers/PreferredSizeStylerEditor.cpp b/src/ThemeBuilder/components/stylers/PreferredSizeStylerEditor.cpp
new file mode 100644
index 00000000..fb713c8c
--- /dev/null
+++ b/src/ThemeBuilder/components/stylers/PreferredSizeStylerEditor.cpp
@@ -0,0 +1,34 @@
+#include "PreferredSizeStylerEditor.h"
+#include "cru/ui/style/Styler.h"
+
+namespace cru::theme_builder::components::stylers {
+PreferredSizeStylerEditor::PreferredSizeStylerEditor() {
+ SetLabel(u"Preferred Size Styler");
+ GetContainer()->AddChild(width_editor_.GetRootControl());
+ GetContainer()->AddChild(height_editor_.GetRootControl());
+
+ width_editor_.SetLabel(u"Width");
+ height_editor_.SetLabel(u"Height");
+
+ ConnectChangeEvent(width_editor_);
+ ConnectChangeEvent(height_editor_);
+}
+
+PreferredSizeStylerEditor::~PreferredSizeStylerEditor() {}
+
+ClonablePtr<ui::style::PreferredSizeStyler>
+PreferredSizeStylerEditor::GetValue() {
+ return ui::style::PreferredSizeStyler::Create(ui::render::MeasureSize{
+ width_editor_.GetValue(), height_editor_.GetValue()});
+}
+
+void PreferredSizeStylerEditor::SetValue(ui::style::PreferredSizeStyler* styler,
+ bool trigger_change) {
+ width_editor_.SetValue(styler->GetPreferredSize().width, false);
+ height_editor_.SetValue(styler->GetPreferredSize().height, false);
+
+ if (trigger_change) {
+ RaiseChangeEvent();
+ }
+}
+} // namespace cru::theme_builder::components::stylers
diff --git a/src/ThemeBuilder/components/stylers/PreferredSizeStylerEditor.h b/src/ThemeBuilder/components/stylers/PreferredSizeStylerEditor.h
new file mode 100644
index 00000000..4a64da10
--- /dev/null
+++ b/src/ThemeBuilder/components/stylers/PreferredSizeStylerEditor.h
@@ -0,0 +1,27 @@
+#pragma once
+#include "../properties/MeasureLengthPropertyEditor.h"
+#include "StylerEditor.h"
+#include "cru/common/ClonablePtr.h"
+#include "cru/ui/style/Styler.h"
+
+namespace cru::theme_builder::components::stylers {
+class PreferredSizeStylerEditor : public StylerEditor {
+ public:
+ PreferredSizeStylerEditor();
+ ~PreferredSizeStylerEditor() override;
+
+ ClonablePtr<ui::style::PreferredSizeStyler> GetValue();
+ void SetValue(ui::style::PreferredSizeStyler* styler,
+ bool trigger_change = true);
+ void SetValue(const ClonablePtr<ui::style::PreferredSizeStyler>& styler,
+ bool trigger_change = true) {
+ SetValue(styler.get(), trigger_change);
+ }
+
+ ClonablePtr<ui::style::Styler> GetStyler() override { return GetValue(); }
+
+ private:
+ properties::MeasureLengthPropertyEditor width_editor_;
+ properties::MeasureLengthPropertyEditor height_editor_;
+};
+} // namespace cru::theme_builder::components::stylers
diff --git a/src/ThemeBuilder/components/stylers/StylerEditor.cpp b/src/ThemeBuilder/components/stylers/StylerEditor.cpp
new file mode 100644
index 00000000..0348adbd
--- /dev/null
+++ b/src/ThemeBuilder/components/stylers/StylerEditor.cpp
@@ -0,0 +1,63 @@
+#include "StylerEditor.h"
+#include "../Common.h"
+#include "BorderStylerEditor.h"
+#include "CompoundStylerEditor.h"
+#include "ContentBrushStylerEditor.h"
+#include "CursorStylerEditor.h"
+#include "FontStylerEditor.h"
+#include "MarginStylerEditor.h"
+#include "PaddingStylerEditor.h"
+#include "PreferredSizeStylerEditor.h"
+#include "cru/ui/controls/FlexLayout.h"
+#include "cru/ui/render/FlexLayoutRenderObject.h"
+#include "cru/ui/style/Styler.h"
+
+namespace cru::theme_builder::components::stylers {
+StylerEditor::StylerEditor() {}
+
+StylerEditor::~StylerEditor() {}
+
+std::unique_ptr<StylerEditor> CreateStylerEditor(ui::style::Styler* styler) {
+ if (auto compound_styler = dynamic_cast<ui::style::CompoundStyler*>(styler)) {
+ auto result = std::make_unique<CompoundStylerEditor>();
+ result->SetValue(compound_styler);
+ return result;
+ } else if (auto border_styler =
+ dynamic_cast<ui::style::BorderStyler*>(styler)) {
+ auto editor = std::make_unique<BorderStylerEditor>();
+ editor->SetValue(border_styler);
+ return editor;
+ } else if (auto cursor_styler =
+ dynamic_cast<ui::style::CursorStyler*>(styler)) {
+ auto editor = std::make_unique<CursorStylerEditor>();
+ editor->SetValue(cursor_styler);
+ return editor;
+ } else if (auto preferred_size_styler =
+ dynamic_cast<ui::style::PreferredSizeStyler*>(styler)) {
+ auto editor = std::make_unique<PreferredSizeStylerEditor>();
+ editor->SetValue(preferred_size_styler);
+ return editor;
+ } else if (auto margin_styler =
+ dynamic_cast<ui::style::MarginStyler*>(styler)) {
+ auto editor = std::make_unique<MarginStylerEditor>();
+ editor->SetValue(margin_styler);
+ return editor;
+ } else if (auto padding_styler =
+ dynamic_cast<ui::style::PaddingStyler*>(styler)) {
+ auto editor = std::make_unique<PaddingStylerEditor>();
+ editor->SetValue(padding_styler);
+ return editor;
+ } else if (auto content_brush_styler =
+ dynamic_cast<ui::style::ContentBrushStyler*>(styler)) {
+ auto editor = std::make_unique<ContentBrushStylerEditor>();
+ editor->SetValue(content_brush_styler);
+ return editor;
+ } else if (auto font_styler = dynamic_cast<ui::style::FontStyler*>(styler)) {
+ auto editor = std::make_unique<FontStylerEditor>();
+ editor->SetValue(font_styler);
+ return editor;
+ } else {
+ throw Exception(u"Unknown styler type");
+ }
+}
+} // namespace cru::theme_builder::components::stylers
diff --git a/src/ThemeBuilder/components/stylers/StylerEditor.h b/src/ThemeBuilder/components/stylers/StylerEditor.h
new file mode 100644
index 00000000..8aa52bda
--- /dev/null
+++ b/src/ThemeBuilder/components/stylers/StylerEditor.h
@@ -0,0 +1,16 @@
+#pragma once
+#include "../HeadBodyEditor.h"
+#include "cru/ui/style/Styler.h"
+
+namespace cru::theme_builder::components::stylers {
+class StylerEditor : public HeadBodyEditor {
+ public:
+ StylerEditor();
+ ~StylerEditor() override;
+
+ public:
+ virtual ClonablePtr<ui::style::Styler> GetStyler() = 0;
+};
+
+std::unique_ptr<StylerEditor> CreateStylerEditor(ui::style::Styler* styler);
+} // namespace cru::theme_builder::components::stylers
diff --git a/src/ThemeBuilder/main.cpp b/src/ThemeBuilder/main.cpp
new file mode 100644
index 00000000..0c5b2159
--- /dev/null
+++ b/src/ThemeBuilder/main.cpp
@@ -0,0 +1,25 @@
+#include "components/MainWindow.h"
+#include "cru/common/io/Resource.h"
+#include "cru/platform/bootstrap/Bootstrap.h"
+#include "cru/ui/ThemeManager.h"
+#include "cru/ui/ThemeResourceDictionary.h"
+
+int main() {
+ using namespace cru::theme_builder::components;
+ using namespace cru::ui;
+
+ auto resource_dir = cru::io::GetResourceDir();
+
+ ThemeManager::GetInstance()->PrependThemeResourceDictionary(
+ ThemeResourceDictionary::FromFile(
+ resource_dir / "cru/theme_builder/ThemeResources.xml"));
+
+ std::unique_ptr<cru::platform::gui::IUiApplication> application(
+ cru::platform::bootstrap::CreateUiApplication());
+
+ auto main_window = std::make_unique<MainWindow>();
+
+ main_window->Show();
+
+ return application->Run();
+}