<|start_filename|>src/tools/editor/src/scene/scene_editor_window.h<|end_filename|>
#pragma once
#include "choose_asset_window.h"
#include "entity_editor.h"
#include "entity_list.h"
#include "halley/ui/ui_widget.h"
#include "scene_editor_canvas.h"
#include "halley/tools/dll/dynamic_library.h"
#include "undo_stack.h"
#include "halley/tools/dll/project_dll.h"

namespace Halley {
	class ProjectWindow;
	class AssetBrowserTabs;
	class HalleyAPI;
	class Project;
	class UIFactory;
	class EntityFactory;

	class SceneEditorWindow final : public UIWidget, public IProjectDLLListener, public ISceneEditorWindow {
	public:
		SceneEditorWindow(UIFactory& factory, Project& project, const HalleyAPI& api, ProjectWindow& projectWindow);
		~SceneEditorWindow();

		void onAddedToRoot(UIRoot& root) override;
		
		void loadScene(const String& sceneName);
		void loadPrefab(const String& name);
		void loadScene(AssetType type, const Prefab& prefab);
		void unloadScene();

		void onEntityAdded(const String& id, const String& parentId, int childIndex) override;
		void onEntityRemoved(const String& id, const String& parentId, int childIndex, const EntityData& prevData) override;
		void onEntityModified(const String& id, const EntityData& prevData, const EntityData& newData) override;
		void onEntityReplaced(const String& id, const String& parentId, int childIndex, const EntityData& prevData, const EntityData& newData) override;
		void onEntityMoved(const String& id, const String& prevParentId, int prevChildIndex, const String& newParentId, int newChildIndex) override;
		void onComponentRemoved(const String& name) override;
		void onFieldChangedByGizmo(const String& componentName, const String& fieldName) override;

		void addNewEntity(std::optional<String> reference = {}, bool childOfReference = false);
		void addNewPrefab(std::optional<String> reference = {}, bool childOfReference = false);
		std::optional<EntityData> makeInstance(const String& prefab) const;
		void addNewPrefab(const String& referenceEntityId, bool childOfReference, const String& prefabName);
		void addEntity(const String& referenceEntityId, bool childOfReference, EntityData data);
		void addEntity(const String& parentId, int childIndex, EntityData data);
		void removeEntity();
		void removeEntity(const String& entityId) override;
		void replaceEntity(const String& entityId, EntityData newData);
		void selectEntity(const String& id);
		void modifyEntity(const String& id, const EntityDataDelta& delta);
		void moveEntity(const String& id, const String& newParent, int childIndex, bool refreshEntityList = true);

		void extractPrefab(const String& id);
		void extractPrefab(const String& id, const String& prefabName);
		void collapsePrefab(const String& id);

		void setTool(String tool);
		void setTool(String tool, String componentName, String fieldName);

		std::shared_ptr<const Prefab> getGamePrefab(const String& id) const;

		void copyEntityToClipboard(const String& id);
		void cutEntityToClipboard(const String& id);
		void pasteEntityFromClipboard(const String& referenceId, bool childOfReference);
		String copyEntity(const String& id);
		void pasteEntity(const String& data, const String& referenceId, bool childOfReference);
		void duplicateEntity(const String& id);
		void openEditPrefabWindow(const String& name);

		const std::shared_ptr<ISceneData>& getSceneData() const override;

		void markModified() override;
		void clearModifiedFlag();
		bool isModified() const;
		void saveScene();

		const EntityIcons& getEntityIcons() const;

		void refreshAssets();

		void addComponentToCurrentEntity(const String& componentName) override;
		void setHighlightedComponents(std::vector<String> componentNames) override;
		const IEntityEditorFactory& getEntityEditorFactory() const override;

		std::shared_ptr<ScriptNodeTypeCollection> getScriptNodeTypes() override;
		
		const ConfigNode& getSetting(EditorSettingType type, std::string_view id) const override;
		void setSetting(EditorSettingType type, std::string_view id, ConfigNode data) override;

		Path getPrimaryInputFile(AssetType type, const String& assetId, bool absolute) const override;

		String getCurrentAssetId() const override;
		
		void onTabbedIn();

		float getProjectDefaultZoom() const override;

		std::shared_ptr<EntityFactory> getEntityFactory() const override;
		void spawnUI(std::shared_ptr<UIWidget> ui) override;
		
		void openAsset(AssetType assetType, const String& assetId);
		void openAssetHere(AssetType assetType, const String& assetId);
		String getAssetKey() const;

		std::vector<AssetCategoryFilter> getPrefabCategoryFilters() const;
		
		Future<AssetPreviewData> getAssetPreviewData(AssetType assetType, const String& id, Vector2i size);

		void onEntityContextMenuAction(const String& actionId, const String& entityId);
		bool canPasteEntity() const;
		bool canAddSibling(const String& entityId) const;
		bool isPrefabInstance(const String& entityId) const;

	protected:
		void update(Time t, bool moved) override;

		bool onKeyPress(KeyboardKeyPress key) override;

		void onProjectDLLStatusChange(ProjectDLL::Status status) override;
	
	private:		
		const HalleyAPI& api;
		UIFactory& uiFactory;
		Project& project;
		ProjectWindow& projectWindow;

		std::shared_ptr<SceneEditorGameBridge> gameBridge;
		std::shared_ptr<SceneEditorCanvas> canvas;
		std::shared_ptr<EntityList> entityList;
		std::shared_ptr<EntityEditor> entityEditor;
		std::shared_ptr<EntityEditorFactory> entityEditorFactory;
		std::shared_ptr<UIList> toolMode;
		std::shared_ptr<EntityIcons> entityIcons;

		Path assetPath;
		std::shared_ptr<ISceneData> sceneData;
		std::shared_ptr<Prefab> prefab;
		AssetType origPrefabAssetType;
		std::shared_ptr<EntityFactory> entityFactory;
		std::optional<EntityScene> currentEntityScene;

		String currentEntityId;

		std::shared_ptr<UIWidget> curCustomUI;
		std::shared_ptr<UIWidget> curToolUI;
		String curTool;
		String curComponentName;

		int toolModeTimeout = 1; // Hack

		UndoStack undoStack;
		bool modified = false;
		bool buttonsNeedUpdate = false;
		
		size_t assetReloadCallbackIdx = 0;
		std::vector<std::function<bool(gsl::span<const String>)>> assetReloadCallbacks;

		void makeUI();
		void onEntitySelected(const String& id);
		void panCameraToEntity(const String& id);

		String findParent(const String& entityId) const;
		const String* findParent(const String& entityId, const EntityTree& tree, const String& prev) const;
		String getNextSibling(const String& parentId, int childIndex) const;
		std::pair<String, int> getParentInsertPos(const String& referenceId, bool childOfReference) const;

		void setCustomUI(std::shared_ptr<UIWidget> ui);
		void setToolUI(std::shared_ptr<UIWidget> ui);

		void setModified(bool enabled);

		String serializeEntity(const EntityData& node) const;
		std::optional<EntityData> deserializeEntity(const String& data) const;

		void assignUUIDs(EntityData& node);
		void positionEntityAtCursor(EntityData& entityData, const String& parentId) const;
		void positionEntity(EntityData& entityData, Vector2f pos) const;
		bool isValidEntityTree(const ConfigNode& node) const;

		void toggleConsole();
		void setupConsoleCommands();

		void updateButtons();

		void undo();
		void redo();
	};
}


<|start_filename|>src/tools/editor/src/assets/asset_browser_tabs.cpp<|end_filename|>
#include "asset_browser_tabs.h"

#include "asset_editor_window.h"
#include "halley/tools/project/project.h"
#include "src/ui/editor_ui_factory.h"
#include "src/ui/project_window.h"

using namespace Halley;

AssetBrowserTabs::AssetBrowserTabs(EditorUIFactory& factory, Project& project, ProjectWindow& projectWindow)
	: UIWidget("asset_browser_tabs", {}, UISizer(UISizerType::Horizontal))
	, factory(factory)
	, project(project)
	, projectWindow(projectWindow)
{
	makeUI();
}

void AssetBrowserTabs::load(std::optional<AssetType> assetType, const String& name)
{
	openTab(assetType, name, true);
	saveTabs();
}

void AssetBrowserTabs::openTab(std::optional<AssetType> assetType, const String& name, bool selected)
{
	const String key = assetType ? (toString(assetType) + ":" + name) : name;

	const bool alreadyExists = tabs->setSelectedOptionId(key);
	if (alreadyExists) {
		return;
	}

	// Create tab
	auto tabContents = factory.makeUI("ui/halley/asset_browser_tab_contents");
	tabContents->setId("tabContents");
	populateTab(*tabContents, assetType, name, key);
	tabs->addItem(key, tabContents);

	// Create window
	auto window = std::make_shared<AssetEditorWindow>(factory, project, projectWindow);
	window->setAssetSrcMode(srcMode);
	window->loadAsset(name, assetType);
	windows.push_back(window);

	// Add to tabs
	pages->addPage()->add(window, 1);
	layout();
	if (selected) {
		tabs->setSelectedOption(int(tabs->getCount()) - 1);
	}
}

bool AssetBrowserTabs::closeTab(const String& key)
{
	tabs->setSelectedOptionId(key);
	auto idx = tabs->getSelectedOption();

	if (windows[idx]->isModified()) {
		if (getRoot() && !getRoot()->hasModalUI()) {
			auto buttons = { UIConfirmationPopup::ButtonType::Yes, UIConfirmationPopup::ButtonType::No, UIConfirmationPopup::ButtonType::Cancel };
			auto callback = [this, idx, key] (UIConfirmationPopup::ButtonType buttonType)
			{
				if (buttonType == UIConfirmationPopup::ButtonType::Cancel) {
					std_ex::erase_if(toClose, [&] (const auto& v) { return v == key; });
				} else {
					if (buttonType == UIConfirmationPopup::ButtonType::Yes) {
						windows[idx]->save();
					}
					doCloseTab(key);
				}
			};

			getRoot()->addChild(std::make_shared<UIConfirmationPopup>(factory, "Save Changes?", "Would you like to save your changes to " + windows[idx]->getName() + " before closing the tab?", buttons, std::move(callback)));
		}

		std_ex::erase_if(toClose, [&] (const auto& v) { return v == key; });
		return false;
	} else {
		doCloseTab(key);
	}
	return true;
}

void AssetBrowserTabs::doCloseTab(const String& key)
{
	const auto idx = tabs->tryGetItemId(key);
	if (idx != -1) {
		pages->removePage(idx);
		windows.erase(windows.begin() + idx);
		tabs->removeItem(key);
		saveTabs();
		std_ex::erase_if(toClose, [&] (const auto& v) { return v == key; });
	}
}

bool AssetBrowserTabs::requestQuit(std::function<void()> callback)
{
	if (!getRoot()) {
		return true;
	}
	if (quittingCallback) {
		Logger::logDev("Ignoring quit request due to another quest being in flight");
		return false;
	}
	if (getRoot()->hasModalUI()) {
		Logger::logDev("Ignoring quit request due to a modal UI being present: " + getRoot()->getModalUIName());
		return false;
	}

	quittingCallback = std::move(callback);
	return proceedQuitRequested(0, false);
}

bool AssetBrowserTabs::proceedQuitRequested(size_t idx, bool invoke)
{
	for (size_t i = idx; i < windows.size(); ++i) {
		auto& window = windows[i];
		if (window->isModified()) {
			tabs->setSelectedOption(static_cast<int>(i));
			auto buttons = { UIConfirmationPopup::ButtonType::Yes, UIConfirmationPopup::ButtonType::No, UIConfirmationPopup::ButtonType::Cancel };
			auto callback = [this, i] (UIConfirmationPopup::ButtonType buttonType)
			{
				if (buttonType == UIConfirmationPopup::ButtonType::Cancel) {
					quittingCallback = {};
				} else {
					if (buttonType == UIConfirmationPopup::ButtonType::Yes) {
						windows[i]->save();
					}
					proceedQuitRequested(i + 1, true);
				}
			};
			getRoot()->addChild(std::make_shared<UIConfirmationPopup>(factory, "Save Changes?", "Would you like to save your changes to " + window->getName() + " before closing the project?", buttons, std::move(callback)));
			return false;
		}
	}

	if (invoke) {
		quittingCallback();
	}
	quittingCallback = {};

	return true;
}

void AssetBrowserTabs::populateTab(UIWidget& tab, std::optional<AssetType> assetType, const String& name, const String& key)
{
	Sprite icon;
	if (assetType) {
		icon = factory.makeAssetTypeIcon(assetType.value());
	} else {
		const auto type = project.getAssetImporter()->getImportAssetType(name, false);
		icon = factory.makeImportAssetTypeIcon(type);
	}
	auto label = LocalisedString::fromUserString(Path(name).getFilename().toString());
	tab.getWidgetAs<UIImage>("icon")->setSprite(icon);
	tab.getWidgetAs<UILabel>("label")->setText(std::move(label));
	tab.setHandle(UIEventType::ButtonClicked, "close", [=] (const UIEvent& event)
	{
		toClose.push_back(key);
	});
}

void AssetBrowserTabs::replaceAssetTab(const String& oldName, const String& newName)
{
	const auto idx = tabs->tryGetItemId(oldName);
	if (idx == -1) {
		return;
	}

	const auto contents = tabs->getItem(idx)->getWidget("tabContents");
	if (contents) {
		tabs->changeItemId(idx, newName);
		populateTab(*contents, {}, newName, newName);
		windows[idx]->loadAsset(newName, {});
		saveTabs();
	}
}

void AssetBrowserTabs::refreshAssets()
{
	for (int i = 0; i < pages->getNumberOfPages(); ++i) {
		auto assetEditor = pages->getPage(i)->getWidgetAs<AssetEditorWindow>("assetEditorWindow");
		assetEditor->refreshAssets();
	}
}

void AssetBrowserTabs::setAssetSrcMode(bool srcMode)
{
	this->srcMode = srcMode;

	if (waitingLoad) {
		waitingLoad = false;
		loadTabs();
	}
}

void AssetBrowserTabs::saveCurrentTab()
{
	const int curPage = pages->getCurrentPage();
	if (curPage >= 0 && curPage < static_cast<int>(windows.size())) {
		windows[curPage]->save();
	}
}

void AssetBrowserTabs::saveAllTabs()
{
	for (auto& window: windows) {
		window->save();
	}
}

void AssetBrowserTabs::closeCurrentTab()
{
	const int curPage = pages->getCurrentPage();
	if (curPage >= 0 && curPage < static_cast<int>(windows.size())) {
		closeTab(tabs->getItem(curPage)->getId());
	}
}

void AssetBrowserTabs::moveTabFocus(int delta)
{
	const int nTabs = int(tabs->getCount());
	if (nTabs > 0) {
		tabs->setSelectedOption(modulo(tabs->getSelectedOption() + delta, nTabs));
	}
}

void AssetBrowserTabs::update(Time t, bool moved)
{
	auto closing = toClose;
	for (auto& close: closing) {
		closeTab(close);
	}

	int size = static_cast<int>(windows.size());
	for (int i = 0; i < size; ++i) {
		tabs->getItem(i)->getWidget("modified")->setActive(windows[i]->isModified());
	}
}

void AssetBrowserTabs::makeUI()
{
	add(factory.makeUI("ui/halley/asset_browser_tabs"), 1);
	tabs = getWidgetAs<UIList>("tabs");
	tabs->setDragEnabled(true);
	pages = getWidgetAs<UIPagedPane>("pages");

	setHandle(UIEventType::ListSelectionChanged, "tabs", [=] (const UIEvent& event)
	{
		pages->setPage(event.getIntData());
		saveTabs();
	});

	setHandle(UIEventType::ListItemMiddleClicked, "tabs", [=] (const UIEvent& event)
	{
		closeTab(event.getStringData());
	});

	setHandle(UIEventType::ListItemRightClicked, [=] (const UIEvent& event)
	{
		openContextMenu(event.getStringData());
	});

	setHandle(UIEventType::ListItemsSwapped, [=] (const UIEvent& event)
	{
		const int a = event.getIntData();
		const int b = event.getIntData2();
		pages->swapPages(a, b);
		std::swap(windows[a], windows[b]);
	});
}

void AssetBrowserTabs::saveTabs()
{
	ConfigNode::SequenceType tabIds;
	const auto n = tabs->getCount();
	for (int i = 0; i < n; ++i) {
		tabIds.push_back(ConfigNode(tabs->getItem(i)->getId()));
	}

	projectWindow.setSetting(EditorSettingType::Project, "tabsOpen", std::move(tabIds));
	projectWindow.setSetting(EditorSettingType::Project, "currentTab", ConfigNode(tabs->getSelectedOptionId()));
}

void AssetBrowserTabs::loadTabs()
{
	const auto& tabsOpen = projectWindow.getSetting(EditorSettingType::Project, "tabsOpen");
	if (tabsOpen.getType() == ConfigNodeType::Sequence) {
		for (const auto& tab: tabsOpen) {
			auto id = tab.asString();
			auto splitParts = id.split(':');
			if (splitParts.size() == 2) {
				openTab(fromString<AssetType>(splitParts[0]), splitParts[1], false);
			} else if (splitParts.size() == 1) {
				openTab({}, splitParts[0], false);
			}
		}
	}

	const auto curTab = projectWindow.getSetting(EditorSettingType::Project, "currentTab").asString("");
	tabs->setSelectedOptionId(curTab);
}

void AssetBrowserTabs::openContextMenu(const String& tabId)
{
	auto menuOptions = std::vector<UIPopupMenuItem>();
	auto makeEntry = [&] (const String& id, const String& text, const String& toolTip, const String& icon, bool enabled = true)
	{
		auto iconSprite = Sprite().setImage(factory.getResources(), "entity_icons/" + (icon.isEmpty() ? "empty.png" : icon));
		menuOptions.push_back(UIPopupMenuItem(id, LocalisedString::fromHardcodedString(text), std::move(iconSprite), LocalisedString::fromHardcodedString(toolTip)));
		menuOptions.back().enabled = enabled;
	};

	makeEntry("close_tab", "Close", "Close this tab. [Ctrl+W]", "", true);
	makeEntry("close_other_tabs", "Close Others", "Close all other tabs.", "", true);
	makeEntry("close_to_the_right", "Close to the Right", "Close all tabs to the right.", "", true);
	makeEntry("close_all", "Close All", "Close all tabs.", "", true);

	auto menu = std::make_shared<UIPopupMenu>("entity_list_context_menu", factory.getStyle("popupMenu"), menuOptions);
	menu->spawnOnRoot(*getRoot());

	menu->setHandle(UIEventType::PopupAccept, [this, tabId] (const UIEvent& e) {\
		Concurrent::execute(Executors::getMainThread(), [=] () {
			onContextMenuAction(e.getStringData(), tabId);
		});
	});
}

void AssetBrowserTabs::onContextMenuAction(const String& action, const String& tabId)
{
	const int nTabs = static_cast<int>(tabs->getCount());

	if (action == "close_tab") {
		toClose.push_back(tabId);
	} else if (action == "close_other_tabs") {
		for (int i = 0; i < nTabs; ++i) {
			const auto& id = tabs->getItem(i)->getId();
			if (id != tabId) {
				toClose.push_back(tabs->getItem(i)->getId());
			}
		}
	} else if (action == "close_to_the_right") {
		bool enabled = false;
		for (int i = 0; i < nTabs; ++i) {
			const auto& id = tabs->getItem(i)->getId();
			if (id == tabId) {
				enabled = true;
			} else if (enabled) {
				toClose.push_back(id);
			}
		}
	} else if (action == "close_all") {
		for (int i = 0; i < nTabs; ++i) {
			const auto& id = tabs->getItem(i)->getId();
			toClose.push_back(id);
		}
	}
}


<|start_filename|>src/engine/ui/src/ui/ui_factory.cpp<|end_filename|>
#include <utility>
#include "halley/file_formats/config_file.h"
#include "halley/core/api/halley_api.h"
#include "halley/ui/ui_factory.h"
#include "halley/ui/ui_widget.h"
#include "halley/ui/widgets/ui_label.h"
#include "halley/ui/widgets/ui_button.h"
#include "halley/ui/widgets/ui_textinput.h"
#include "halley/ui/widgets/ui_spin_control.h"
#include "halley/ui/widgets/ui_list.h"
#include "halley/ui/widgets/ui_dropdown.h"
#include "halley/ui/widgets/ui_image.h"
#include "halley/ui/widgets/ui_multi_image.h"
#include "halley/ui/widgets/ui_animation.h"
#include "halley/ui/widgets/ui_scrollbar.h"
#include "halley/ui/widgets/ui_scroll_pane.h"
#include "halley/ui/widgets/ui_scrollbar_pane.h"
#include "halley/ui/widgets/ui_checkbox.h"
#include "halley/ui/widgets/ui_slider.h"
#include "halley/ui/widgets/ui_paged_pane.h"
#include "halley/support/logger.h"
#include "ui_validator.h"
#include "halley/ui/widgets/ui_framed_image.h"
#include "halley/ui/widgets/ui_hybrid_list.h"
#include "widgets/ui_spin_list.h"
#include "widgets/ui_option_list_morpher.h"
#include "widgets/ui_tree_list.h"
#include "halley/ui/behaviours/ui_reload_ui_behaviour.h"
#include "widgets/ui_debug_console.h"
#include "widgets/ui_spin_control2.h"

using namespace Halley;

UIFactory::UIFactory(const HalleyAPI& api, Resources& resources, const I18N& i18n, std::shared_ptr<UIStyleSheet> styleSheet, std::shared_ptr<const UIColourScheme> colourScheme)
	: api(api)
	, resources(resources)
	, i18n(i18n)
	, colourScheme(std::move(colourScheme))
	, styleSheet(std::move(styleSheet))
{
	if (!this->colourScheme) {
		loadDefaultColourScheme();
	}
	
	addFactory("widget", [=] (const ConfigNode& node) { return makeBaseWidget(node); });
	addFactory("label", [=] (const ConfigNode& node) { return makeLabel(node); });
	addFactory("button", [=] (const ConfigNode& node) { return makeButton(node); });
	addFactory("textInput", [=] (const ConfigNode& node) { return makeTextInput(node); });
	addFactory("spinControl", [=] (const ConfigNode& node) { return makeSpinControl(node); });
	addFactory("spinControl2", [=] (const ConfigNode& node) { return makeSpinControl2(node); });
	addFactory("list", [=] (const ConfigNode& node) { return makeList(node); });
	addFactory("dropdown", [=] (const ConfigNode& node) { return makeDropdown(node); });
	addFactory("checkbox", [=] (const ConfigNode& node) { return makeCheckbox(node); });
	addFactory("image", [=] (const ConfigNode& node) { return makeImage(node); });
	addFactory("multiImage", [=](const ConfigNode& node) { return makeMultiImage(node); });
	addFactory("animation", [=] (const ConfigNode& node) { return makeAnimation(node); });
	addFactory("scrollBar", [=] (const ConfigNode& node) { return makeScrollBar(node); });
	addFactory("scrollPane", [=] (const ConfigNode& node) { return makeScrollPane(node); });
	addFactory("scrollBarPane", [=] (const ConfigNode& node) { return makeScrollBarPane(node); });
	addFactory("slider", [=] (const ConfigNode& node) { return makeSlider(node); });
	addFactory("horizontalDiv", [=] (const ConfigNode& node) { return makeHorizontalDiv(node); });
	addFactory("verticalDiv", [=] (const ConfigNode& node) { return makeVerticalDiv(node); });
	addFactory("tabbedPane", [=] (const ConfigNode& node) { return makeTabbedPane(node); });
	addFactory("pagedPane", [=] (const ConfigNode& node) { return makePagedPane(node); });
	addFactory("framedImage", [=] (const ConfigNode& node) { return makeFramedImage(node); });
	addFactory("hybridList", [=] (const ConfigNode& node) { return makeHybridList(node); });
	addFactory("spinList", [=](const ConfigNode& node) { return makeSpinList(node); });
	addFactory("optionListMorpher", [=](const ConfigNode& node) { return makeOptionListMorpher(node); });
	addFactory("treeList", [=](const ConfigNode& node) { return makeTreeList(node); });
	addFactory("debugConsole", [=](const ConfigNode& node) { return makeDebugConsole(node); });
}

UIFactory::~UIFactory()
{
}

void UIFactory::addFactory(const String& key, WidgetFactory factory)
{
	factories[key] = factory;
}

void UIFactory::pushConditions(std::vector<String> conds)
{
	conditionStack.push_back(conds.size());
	for (auto& c: conds) {
		conditions.emplace_back(std::move(c));
	}
}

void UIFactory::popConditions()
{
	if (conditionStack.empty()) {
		throw Exception("No conditions to pop!", HalleyExceptions::UI);
	}
	const size_t n = conditionStack.back();

	conditions.erase(conditions.begin() + (conditions.size() - n), conditions.end());
	conditionStack.pop_back();
}

std::shared_ptr<UIWidget> UIFactory::makeUI(const String& configName)
{
	return makeUIFromNode(resources.get<ConfigFile>(configName)->getRoot());
}

std::shared_ptr<UIWidget> UIFactory::makeUI(const String& configName, std::vector<String> conditions)
{
	pushConditions(std::move(conditions));
	try {
		auto result = makeUI(configName);
		popConditions();
		return result;
	} catch (...) {
		popConditions();
		throw;
	}
}

std::shared_ptr<UIWidget> UIFactory::makeUIFromNode(const ConfigNode& node)
{
	return makeWidget(node);
}

void UIFactory::loadUI(UIWidget& target, const String& configName)
{
	loadUI(target, *resources.get<ConfigFile>(configName));
}

void UIFactory::loadUI(UIWidget& target, const ConfigFile& configFile)
{
	try {
		target.add(makeUIFromNode(configFile.getRoot()), 1);
		target.onMakeUI();
	} catch (const std::exception& e) {
		Logger::logException(e);
	}
	
	target.addBehaviour(std::make_shared<UIReloadUIBehaviour>(*this, ConfigObserver(configFile)));
}

void UIFactory::setInputButtons(const String& key, UIInputButtons buttons)
{
	inputButtons[key] = buttons;
}

UIStyle UIFactory::getStyle(const String& name) const
{
	return UIStyle(name, styleSheet);
}

std::shared_ptr<UIStyleSheet> UIFactory::getStyleSheet() const
{
	return styleSheet;
}

Resources& UIFactory::getResources() const
{
	return resources;
}

std::unique_ptr<UIFactory> UIFactory::withResources(Resources& newResources) const
{
	auto result = std::make_unique<UIFactory>(api, newResources, i18n, styleSheet, colourScheme);
	result->inputButtons = inputButtons;
	return result;
}

const I18N& UIFactory::getI18N() const
{
	return i18n;
}

void UIFactory::setStyleSheet(std::shared_ptr<UIStyleSheet> styleSheet)
{
	this->styleSheet = std::move(styleSheet);
}

std::shared_ptr<const UIColourScheme> UIFactory::getColourScheme() const
{
	return colourScheme;
}

void UIFactory::update()
{
	styleSheet->updateIfNeeded();
}

Sprite UIFactory::makeAssetTypeIcon(AssetType type) const
{
	return Sprite();
}

std::shared_ptr<UIWidget> UIFactory::makeWidget(const ConfigNode& entryNode)
{
	styleSheet->updateIfNeeded();
	
	auto& widgetNode = entryNode["widget"];
	auto widgetClass = widgetNode["class"].asString();
	auto iter = factories.find(widgetClass);
	if (iter == factories.end()) {
		throw Exception("Unknown widget class: " + widgetClass, HalleyExceptions::UI);
	}
	
	auto widget = iter->second(entryNode);
	if (widgetNode.hasKey("size")) {
		widget->setMinSize(asVector2f(widgetNode["size"], {}));
	}
	if (widgetNode.hasKey("enabled")) {
		widget->setEnabled(widgetNode["enabled"].asBool(true));
	}
	if (widgetNode.hasKey("active")) {
		widget->setActive(widgetNode["active"].asBool(true));
	}
	if (widgetNode.hasKey("childLayerAdjustment")) {
		widget->setChildLayerAdjustment(widgetNode["childLayerAdjustment"].asInt());
	}
	if (widgetNode.hasKey("tooltip")) {
		widget->setToolTip(LocalisedString::fromUserString(widgetNode["tooltip"].asString()));
	}
	if (widgetNode.hasKey("tooltipKey")) {
		widget->setToolTip(i18n.get(widgetNode["tooltipKey"].asString()));
	}
	return widget;
}

std::optional<UISizer> UIFactory::makeSizer(const ConfigNode& entryNode)
{
	const bool hasSizer = entryNode.hasKey("sizer");
	const bool hasChildren = entryNode.hasKey("children");
	if (!hasSizer && !hasChildren) {
		return {};
	}

	UISizer sizer;
	
	if (hasSizer) {
		auto& sizerNode = entryNode["sizer"];
		auto sizerType = fromString<UISizerType>(sizerNode["type"].asString("horizontal"));
		float gap = sizerNode["gap"].asFloat(1.0f);
		int nColumns = sizerNode["columns"].asInt(1);
		
		sizer = UISizer(sizerType, gap, nColumns);

		if (sizerNode["columnProportions"].getType() == ConfigNodeType::Sequence) {
			auto& seq = sizerNode["columnProportions"].asSequence();
			std::vector<float> proportions;
			proportions.reserve(seq.size());
			for (auto& e: seq) {
				proportions.push_back(e.asFloat());
			}
			sizer.setColumnProportions(proportions);
		}
	}

	loadSizerChildren(sizer, entryNode["children"]);

	return sizer;
}

UISizer UIFactory::makeSizerOrDefault(const ConfigNode& entryNode, UISizer&& defaultSizer)
{
	auto sizer = makeSizer(entryNode);
	if (sizer) {
		return std::move(sizer.value());
	} else {
		return std::move(defaultSizer);
	}
}

std::shared_ptr<UISizer> UIFactory::makeSizerPtr(const ConfigNode& entryNode)
{
	auto sizer = makeSizer(entryNode);
	if (sizer) {
		return std::make_shared<UISizer>(std::move(sizer.value()));
	} else {
		return {};
	}
}

void UIFactory::loadSizerChildren(UISizer& sizer, const ConfigNode& node)
{
	if (node.getType() == ConfigNodeType::Sequence) {
		for (auto& childNode: node.asSequence()) {
			float proportion = childNode["proportion"].asFloat(0);
			Vector4f border = asVector4f(childNode["border"], Vector4f());
			int fill = 0;

			auto addFill = [&] (const String& fillName)
			{
				if (fillName == "fill") {
					fill |= UISizerFillFlags::Fill;
				} else if (fillName == "fillHorizontal") {
					fill |= UISizerFillFlags::FillHorizontal;
				} else if (fillName == "fillVertical") {
					fill |= UISizerFillFlags::FillVertical;
				} else if (fillName == "centre") {
					fill |= UISizerAlignFlags::Centre;
				} else if (fillName == "left") {
					fill |= UISizerAlignFlags::Left;
				} else if (fillName == "right") {
					fill |= UISizerAlignFlags::Right;
				} else if (fillName == "top") {
					fill |= UISizerAlignFlags::Top;
				} else if (fillName == "bottom") {
					fill |= UISizerAlignFlags::Bottom;
				} else if (fillName == "centreHorizontal") {
					fill |= UISizerAlignFlags::CentreHorizontal;
				} else if (fillName == "centreVertical") {
					fill |= UISizerAlignFlags::CentreVertical;
				}
			};

			if (childNode["fill"].getType() == ConfigNodeType::String) {
				addFill(childNode["fill"].asString());
			} else if (childNode["fill"].getType() == ConfigNodeType::Sequence) {
				for (auto& fillNode: childNode["fill"].asSequence()) {
					addFill(fillNode.asString());
				}
			} else {
				fill = UISizerFillFlags::Fill;
			}

			if (childNode.hasKey("widget")) {
				sizer.add(makeWidget(childNode), proportion, border, fill);
			} else if (childNode.hasKey("sizer") || childNode.hasKey("children")) {
				sizer.add(makeSizerPtr(childNode), proportion, border, fill);
			} else if (childNode.hasKey("spacer")) {
				sizer.addSpacer(proportion);
			} else if (childNode.hasKey("stretchSpacer")) {
				sizer.addStretchSpacer(proportion);
			}
		}
	}
}

void UIFactory::applyInputButtons(UIWidget& widget, const String& key)
{
	if (!key.isEmpty()) {
		auto iter = inputButtons.find(key);
		if (iter == inputButtons.end()) {
			Logger::logWarning("Input buttons binding not found: \"" + key + "\".");
		} else {
			widget.setInputButtons(iter->second);
		}
	}
}

std::optional<Vector2f> UIFactory::asMaybeVector2f(const ConfigNode& node)
{
	if (node.getType() == ConfigNodeType::Sequence) {
		auto seq = node.asSequence();
		return Vector2f(seq.at(0).asFloat(), seq.at(1).asFloat());
	} else {
		return {};
	}
}

Vector2f UIFactory::asVector2f(const ConfigNode& node, std::optional<Vector2f> defaultValue)
{
	if (node.getType() == ConfigNodeType::Sequence) {
		auto seq = node.asSequence();
		return Vector2f(seq.at(0).asFloat(), seq.at(1).asFloat());
	} else if (defaultValue) {
		return defaultValue.value();
	} else {
		throw Exception("Unable to parse node as Vector2f.", HalleyExceptions::UI);
	}
}

LocalisedString UIFactory::parseLabel(const ConfigNode& node, const String& defaultOption, const String& key) {
	LocalisedString label;
	if (node.hasKey(key + "Key")) {
		label = i18n.get(node[key + "Key"].asString());
	} else if (node.hasKey(key)) {
		label = LocalisedString::fromUserString(node[key].asString(defaultOption));
	}
	return label;
}

std::vector<UIFactory::ParsedOption> UIFactory::parseOptions(const ConfigNode& node)
{
	std::vector<ParsedOption> result;
	if (node.getType() == ConfigNodeType::Sequence) {
		for (const auto& n: node.asSequence()) {
			auto id = n["id"].asString("");
			auto label = parseLabel(n, id);
			if (id.isEmpty()) {
				id = label.getString();
			}

			ParsedOption option;
			option.id = id;
			option.text = label;
			option.image = n["image"].asString("");
			option.inactiveImage = n["inactiveImage"].asString("");
			option.spriteSheet = n["spriteSheet"].asString("");
			option.sprite = n["sprite"].asString("");
			option.border = asVector4f(n["border"], Vector4f());
			option.active = n["active"].asBool(true);
			option.tooltip = parseLabel(n, "", "tooltip");
			option.iconColour = n["iconColour"].asString("");
			result.push_back(option);
		}
	}
	return result;
}

Vector4f UIFactory::asVector4f(const ConfigNode& node, std::optional<Vector4f> defaultValue)
{
	if (node.getType() == ConfigNodeType::Sequence) {
		auto seq = node.asSequence();
		return Vector4f(seq.at(0).asFloat(), seq.at(1).asFloat(), seq.at(2).asFloat(), seq.at(3).asFloat());
	} else if (defaultValue) {
		return defaultValue.value();
	} else {
		throw Exception("Unable to parse node as Vector2f.", HalleyExceptions::UI);
	}
}

std::shared_ptr<UIWidget> UIFactory::makeBaseWidget(const ConfigNode& entryNode)
{
	auto& node = entryNode["widget"];
	auto id = node["id"].asString("");
	auto minSize = asVector2f(node["minSize"], Vector2f(0, 0));
	auto innerBorder = asVector4f(node["innerBorder"], Vector4f(0, 0, 0, 0));
	return std::make_shared<UIWidget>(id, minSize, makeSizer(entryNode), innerBorder);
}

std::shared_ptr<UIWidget> UIFactory::makeLabel(const ConfigNode& entryNode)
{
	auto& node = entryNode["widget"];
	auto id = node["id"].asString("");
	auto style = UIStyle(node["style"].asString("label"), styleSheet);
	auto label = std::make_shared<UILabel>(id, std::move(style), parseLabel(node));
	if (node.hasKey("maxWidth")) {
		label->setMaxWidth(node["maxWidth"].asFloat());
	}
	if (node.hasKey("maxHeight")) {
		label->setMaxHeight(node["maxHeight"].asFloat());
	}
	if (node.hasKey("wordWrapped")) {
		label->setWordWrapped(node["wordWrapped"].asBool());
	}
	if (node.hasKey("alignment")) {
		label->setAlignment(node["alignment"].asFloat());
	}
	if (node.hasKey("marquee")) {
		label->setMarquee(node["marquee"].asBool());
	}
	if (node.hasKey("colour")) {
		label->setColour(getColour(node["colour"].asString()));
	}
	return label;
}

std::shared_ptr<UIWidget> UIFactory::makeButton(const ConfigNode& entryNode)
{
	auto& node = entryNode["widget"];
	auto id = node["id"].asString();
	auto style = UIStyle(node["style"].asString("button"), styleSheet);
	auto label = parseLabel(node);

	auto sizer = makeSizerOrDefault(entryNode, UISizer());
	auto result = std::make_shared<UIButton>(id, style, std::move(sizer));

	if (!label.getString().isEmpty()) {
		result->setLabel(LocalisedString::fromUserString(label.getString()));
	}
	
	if (node.hasKey("icon")) {
		result->setIcon(Sprite().setImage(getResources(), node["icon"].asString()));
	}

	if (node.hasKey("mouseBorder")) {
		result->setMouseExtraBorder(asVector4f(node["mouseBorder"], Vector4f()));
	}

	return result;
}

std::shared_ptr<UIWidget> UIFactory::makeTextInput(const ConfigNode& entryNode)
{
	auto& node = entryNode["widget"];
	auto id = node["id"].asString();
	auto style = UIStyle(node["style"].asString("input"), styleSheet);
	auto label = parseLabel(node);
	auto ghostText = parseLabel(node, "", "ghost");

	auto result = std::make_shared<UITextInput>(id, style, "", label);;
	if (!ghostText.getString().isEmpty()) {
		result->setGhostText(ghostText);
	}

	auto validatorName = node["validator"].asString("");
	if (!validatorName.isEmpty()) {
		if (validatorName == "numeric") {
			result->setValidator(std::make_shared<UINumericValidator>(true));
		} else if (validatorName == "numericPositive") {
		result->setValidator(std::make_shared<UINumericValidator>(false));
		} else {
			throw Exception("Unknown text input validator: " + validatorName, HalleyExceptions::UI);
		}
	}

	if (node.hasKey("maxLength")) {
		result->setMaxLength(node["maxLength"].asInt());
	}
	result->setReadOnly(node["readOnly"].asBool(false));
	result->setHistoryEnabled(node["history"].asBool(false));
	result->setClearOnSubmit(node["clearOnSubmit"].asBool(false));

	return result;
}

std::shared_ptr<UIWidget> UIFactory::makeSpinControl(const ConfigNode& entryNode)
{
	auto& node = entryNode["widget"];
	auto id = node["id"].asString();
	auto style = UIStyle(node["style"].asString("spinControl"), styleSheet);

	auto result = std::make_shared<UISpinControl>(id, style, 0.0f);

	if (node.hasKey("minValue")) {
		result->setMinimumValue(node["minValue"].asFloat());
	}
	if (node.hasKey("maxValue")) {
		result->setMaximumValue(node["maxValue"].asFloat());
	}
	if (node.hasKey("increment")) {
		result->setIncrement(node["increment"].asFloat());
	}

	return result;
}

std::shared_ptr<UIWidget> UIFactory::makeSpinControl2(const ConfigNode& entryNode)
{
	auto& node = entryNode["widget"];
	auto id = node["id"].asString();
	auto style = UIStyle(node["style"].asString("spinControl"), styleSheet);

	auto result = std::make_shared<UISpinControl2>(id, style, 0.0f, node["allowFloat"].asBool(false));

	if (node.hasKey("minValue")) {
		result->setMinimumValue(node["minValue"].asFloat());
	}
	if (node.hasKey("maxValue")) {
		result->setMaximumValue(node["maxValue"].asFloat());
	}
	if (node.hasKey("increment")) {
		result->setIncrement(node["increment"].asFloat());
	}

	return result;
}

std::shared_ptr<UIWidget> UIFactory::makeList(const ConfigNode& entryNode)
{
	auto& node = entryNode["widget"];
	auto style = UIStyle(node["style"].asString("list"), styleSheet);
	auto label = parseLabel(node);

	auto orientation = fromString<UISizerType>(node["type"].asString("vertical"));
	int nColumns = node["columns"].asInt(1);
	auto options = parseOptions(node["options"]);

	auto widget = std::make_shared<UIList>(node["id"].asString(), style, orientation, nColumns);
	applyInputButtons(*widget, node["inputButtons"].asString("list"));
	for (auto& o: options) {
		if (!o.image.isEmpty() || !o.sprite.isEmpty()) {
			Sprite normalSprite;
			
			if (!o.image.isEmpty()) {
				normalSprite.setImage(resources, o.image);
			} else {
				normalSprite.setSprite(resources, o.spriteSheet, o.sprite);
			}
			
			auto image = std::make_shared<UIImage>(normalSprite);

			if (!o.inactiveImage.isEmpty()) {
				Sprite inactiveSprite = Sprite().setImage(resources, o.inactiveImage);
				image->setSelectable(inactiveSprite, normalSprite);
				image->setSprite(inactiveSprite);
			}

			widget->addImage(o.id, image, 1, o.border, UISizerAlignFlags::Centre);
		} else {
			widget->addTextItem(o.id, o.text);
		}

		if (!o.tooltip.getString().isEmpty()) {
			widget->getItem(o.id)->setToolTip(o.tooltip);
		}

		widget->setItemActive(o.id, o.active);
	}

	widget->setDragEnabled(node["canDrag"].asBool(false));
	widget->setUniformSizedItems(node["uniformSizedItems"].asBool(false));

	return widget;
}

std::shared_ptr<UIWidget> UIFactory::makeDropdown(const ConfigNode& entryNode)
{
	auto& node = entryNode["widget"];
	auto id = node["id"].asString();
	auto style = UIStyle(node["style"].asString("dropdown"), styleSheet);
	auto label = parseLabel(node);
	auto options = parseOptions(node["options"]);

	std::vector<UIDropdown::Entry> entries;
	entries.reserve(options.size());
	for (auto& o: options) {
		Sprite icon;
		if (!o.image.isEmpty()) {
			icon = Sprite().setImage(getResources(), o.image).setColour(getColour(o.iconColour.isEmpty() ? "#FFFFFF" : o.iconColour));
		}
		entries.emplace_back(o.id, o.text, icon);
	}

	auto widget = std::make_shared<UIDropdown>(id, style);
	applyInputButtons(*widget, node["inputButtons"].asString("list"));
	widget->setOptions(entries);
	return widget;
}

std::shared_ptr<UIWidget> UIFactory::makeCheckbox(const ConfigNode& entryNode)
{
	auto& node = entryNode["widget"];
	auto id = node["id"].asString();
	auto style = UIStyle(node["style"].asString("checkbox"), styleSheet);
	auto checked = node["checked"].asBool(false);

	return std::make_shared<UICheckbox>(id, style, checked);
}

std::shared_ptr<UIWidget> UIFactory::makeImage(const ConfigNode& entryNode)
{
	auto& node = entryNode["widget"];
	auto id = node["id"].asString("");
	auto materialName = node["material"].asString("");
	auto col = node["colour"].asString("#FFFFFF");
	auto flip = node["flip"].asBool(false);
	auto pivot = asMaybeVector2f(node["pivot"]);
	auto rotation = Angle1f::fromDegrees(node["rotation"].asFloat(0.0f));

	auto sprite = Sprite();
	
	if (node.hasKey("image")) {
		auto imageName = node["image"].asString();
		if (colourScheme) {
			sprite = colourScheme->getSprite(resources, imageName, materialName);
		} else {
			sprite = Sprite().setImage(resources, imageName, materialName);
		}
	} else if (node.hasKey("sprite")) {
		auto spriteName = node["sprite"].asString();
		auto spriteSheetName = node["spriteSheet"].asString();
		sprite.setSprite(resources, spriteSheetName, spriteName, materialName);
	}

	sprite.setColour(getColour(col)).setFlip(flip).setRotation(rotation);
	
	if (pivot) {
		sprite.setPivot(pivot.value());
	}
	Vector4f innerBorder = asVector4f(node["innerBorder"], Vector4f());

	auto image = std::make_shared<UIImage>(id, sprite, makeSizer(entryNode), innerBorder);
	if (node.hasKey("layerAdjustment")) {
		image->setLayerAdjustment(node["layerAdjustment"].asInt());
	}
	return image;
}

std::shared_ptr<UIWidget> UIFactory::makeMultiImage(const ConfigNode& entryNode)
{
	const auto& node = entryNode["widget"];
	const auto id = node["id"].asString("");
	const auto size = asVector2f(node["size"], Vector2f());
	const auto materialName = node["material"].asString("");

	std::vector<Sprite> sprites = {};
	std::vector<Vector2f> offsets = {};
	
	if (node.hasKey("images")) {
		const auto& images = node["images"].asSequence();

		for(const auto& imageName : images) {
			Sprite sprite = Sprite();
			sprite.setImage(resources, imageName.asString(), materialName);
		}
	}

	if(node.hasKey("offsets")) {
		offsets = node["offsets"].asVector<Vector2f>();
	}

	return std::make_shared<UIMultiImage>(id, size, sprites, offsets);
}

std::shared_ptr<UIWidget> UIFactory::makeAnimation(const ConfigNode& entryNode)
{
	auto& node = entryNode["widget"];
	auto id = node["id"].asString();
	auto size = asVector2f(node["size"], Vector2f());
	auto animationOffset = asVector2f(node["offset"], Vector2f());
	auto animationName = node["animation"].asString("");
	auto sequence = node["sequence"].asString("default");
	auto direction = node["direction"].asString("default");

	auto animation = AnimationPlayer(animationName.isEmpty() ? std::shared_ptr<const Animation>() : resources.get<Animation>(animationName), sequence, direction);

	return std::make_shared<UIAnimation>(id, size, animationOffset, animation);
}

std::shared_ptr<UIWidget> UIFactory::makeScrollPane(const ConfigNode& entryNode)
{
	auto& node = entryNode["widget"];
	auto id = node["id"].asString("");
	auto clipSize = asVector2f(node["clipSize"], Vector2f());
	auto scrollHorizontal = node["scrollHorizontal"].asBool(false);
	auto scrollVertical = node["scrollVertical"].asBool(true);
	auto mouseWheelEnabled = node["mouseWheelEnabled"].asBool(true);

	auto result = std::make_shared<UIScrollPane>(id, clipSize, makeSizerOrDefault(entryNode, UISizer(UISizerType::Vertical)), scrollHorizontal, scrollVertical);
	result->setScrollWheelEnabled(mouseWheelEnabled);
	return result;
}

std::shared_ptr<UIWidget> UIFactory::makeScrollBar(const ConfigNode& entryNode)
{
	auto& node = entryNode["widget"];
	auto id = node["id"].asString("");
	auto style = UIStyle(node["style"].asString("scrollbar"), styleSheet);
	auto scrollDirection = fromString<UIScrollDirection>(node["scrollDirection"].asString("vertical"));
	auto alwaysShow = !node["autoHide"].asBool(false);

	return std::make_shared<UIScrollBar>(id, scrollDirection, style, alwaysShow);
}

std::shared_ptr<UIWidget> UIFactory::makeScrollBarPane(const ConfigNode& entryNode)
{
	auto& node = entryNode["widget"];
	auto id = node["id"].asString("");
	auto clipSize = asVector2f(node["clipSize"], Vector2f());
	auto style = UIStyle(node["style"].asString("scrollbar"), styleSheet);
	auto scrollHorizontal = node["scrollHorizontal"].asBool(false);
	auto scrollVertical = node["scrollVertical"].asBool(true);
	auto alwaysShow = !node["autoHide"].asBool(false);
	auto mouseWheelEnabled = node["mouseWheelEnabled"].asBool(true);

	auto result = std::make_shared<UIScrollBarPane>(id, clipSize, style, makeSizerOrDefault(entryNode, UISizer(UISizerType::Vertical)), scrollHorizontal, scrollVertical, alwaysShow);
	result->getPane()->setScrollWheelEnabled(mouseWheelEnabled);
	return result;
}

std::shared_ptr<UIWidget> UIFactory::makeSlider(const ConfigNode& entryNode)
{
	auto& node = entryNode["widget"];
	auto id = node["id"].asString();
	auto style = UIStyle(node["style"].asString("slider"), styleSheet);
	auto minValue = node["minValue"].asFloat(0);
	auto maxValue = node["maxValue"].asFloat(1);
	auto value = node["value"].asFloat(0.5f);

	auto slider = std::make_shared<UISlider>(id, style, minValue, maxValue, value);
	if (node.hasKey("granularity")) {
		slider->setGranularity(node["granularity"].asFloat());
	}

	return slider;
}

std::shared_ptr<UIWidget> UIFactory::makeHorizontalDiv(const ConfigNode& entryNode)
{
	const auto& widgetNode = entryNode["widget"];
	auto id = widgetNode["id"].asString("");
	const auto& style = getStyle(widgetNode["style"].asString("horizontalDiv"));
	return std::make_shared<UIImage>(std::move(id), style.getSprite("image"));
}

std::shared_ptr<UIWidget> UIFactory::makeVerticalDiv(const ConfigNode& entryNode)
{
	const auto& widgetNode = entryNode["widget"];
	auto id = widgetNode["id"].asString("");
	auto style = getStyle(widgetNode["style"].asString("verticalDiv"));
	return std::make_shared<UIImage>(id, style.getSprite("image"));
}

std::shared_ptr<UIWidget> UIFactory::makeTabbedPane(const ConfigNode& entryNode)
{
	const auto& widgetNode = entryNode["widget"];
	auto id = widgetNode["id"].asString();
	auto tabs = std::make_shared<UIList>(id, getStyle("tabs"), UISizerType::Horizontal, 1);
	applyInputButtons(*tabs, widgetNode["inputButtons"].asString("tabs"));

	std::vector<const ConfigNode*> tabNodes;
	if (widgetNode.hasKey("tabs")) {
		for (auto& tabNode: widgetNode["tabs"].asSequence()) {
			if (tabNode.hasKey("if")) {
				if (!resolveConditions(tabNode["if"])) {
					continue;
				}
			}
			auto label = parseLabel(tabNode);
			tabs->addTextItem(tabNode["id"].asString(id + "_tab_" + toString(tabNodes.size())), label);
			tabNodes.push_back(&tabNode);
		}
	}

	auto pane = std::make_shared<UIPagedPane>(id + "_pagedPane", int(tabNodes.size()), Vector2f());
	for (int i = 0; i < int(tabNodes.size()); ++i) {
		auto& tabNode = *tabNodes[i];
		pane->getPage(i)->add(makeSizerPtr(tabNode), 1);
	}

	tabs->setHandle(UIEventType::ListSelectionChanged, [pane] (const UIEvent& event)
	{
		pane->setPage(event.getIntData());
		event.getCurWidget().sendEvent(UIEvent(UIEventType::TabChanged, event.getSourceId(), event.getIntData()));
	});

	auto result = std::make_shared<UIWidget>(id + "_container", Vector2f(), UISizer(UISizerType::Vertical));
	result->add(tabs);
	result->add(pane, 1);
	return result;
}

std::shared_ptr<UIWidget> UIFactory::makePagedPane(const ConfigNode& entryNode)
{
	const auto& widgetNode = entryNode["widget"];

	std::vector<const ConfigNode*> pageNodes;
	if (widgetNode.hasKey("pages")) {
		for (auto& pageNode: widgetNode["pages"].asSequence()) {
			if (pageNode.hasKey("if")) {
				if (!resolveConditions(pageNode["if"])) {
					continue;
				}
			}
			pageNodes.push_back(&pageNode);
		}
	}

	auto pane = std::make_shared<UIPagedPane>(widgetNode["id"].asString(), int(pageNodes.size()));
	for (int i = 0; i < int(pageNodes.size()); ++i) {
		auto& pageNode = *pageNodes[i];
		const auto element = pageNode.hasKey("widget") ? std::shared_ptr<IUIElement>(makeWidget(pageNode)) : makeSizerPtr(pageNode);
		pane->getPage(i)->add(element, 1);
	}

	return pane;
}

std::shared_ptr<UIWidget> UIFactory::makeFramedImage(const ConfigNode& entryNode)
{
	auto& node = entryNode["widget"];

	const auto id = node["id"].asString("");
	const auto scrollPos = asVector2f(node["scrollPos"], Vector2f());
	const auto scrollSpeed = asVector2f(node["scrollSpeed"], Vector2f());
	const auto style = getStyle(node["style"].asString());
	
	auto image = std::make_shared<UIFramedImage>(id, style, makeSizer(entryNode));
	image->setScrolling(scrollSpeed, scrollPos);
	return image;
}

std::shared_ptr<UIWidget> UIFactory::makeHybridList(const ConfigNode& node)
{
	auto& widgetNode = node["widget"];
	auto style = getStyle(node["style"].asString("hybridList"));
	auto list = std::make_shared<UIHybridList>(widgetNode["id"].asString(), style);
	if (widgetNode.hasKey("options")) {
		for (auto& optionsNode: widgetNode["options"].asSequence()) {
			if (optionsNode.hasKey("if")) {
				if (!resolveConditions(optionsNode["if"])) {
					continue;
				}
			}

			if (optionsNode["divider"].asBool(false)) {
				list->addDivider(optionsNode["id"].asString(""));
			} else {
				const auto id = optionsNode["id"].asString();
				const auto label = parseLabel(optionsNode);
				list->addTextItem(id, label);
			}
		}
	}
	applyInputButtons(*list, "list");
	return list;
}

std::shared_ptr<UIWidget> UIFactory::makeSpinList(const ConfigNode& entryNode) {
	auto& node = entryNode["widget"];
	auto id = node["id"].asString();
	auto style = UIStyle(node["style"].asString("spinlist"), styleSheet);
	auto label = parseLabel(node);
	auto options = parseOptions(node["options"]);

	std::vector<String> optionIds;
	std::vector<LocalisedString> optionLabels;
	for (auto& o : options) {
		optionIds.push_back(o.id);
		optionLabels.push_back(o.text);
	}

	auto widget = std::make_shared<UISpinList>(id, style);
	applyInputButtons(*widget, node["inputButtons"].asString("list"));
	widget->setOptions(optionIds, optionLabels);
	return widget;
}

std::shared_ptr<UIWidget> UIFactory::makeOptionListMorpher(const ConfigNode& entryNode) {
	auto& node = entryNode["widget"];
	auto id = node["id"].asString();
	auto dropdownStyle = UIStyle(node["dropdownStyle"].asString("dropdown"), styleSheet);
	auto spinlistStyle = UIStyle(node["spinlistStyle"].asString("spinlist"), styleSheet);
	auto label = parseLabel(node);
	auto options = parseOptions(node["options"]);

	std::vector<String> optionIds;
	std::vector<LocalisedString> optionLabels;
	for (auto& o : options) {
		optionIds.push_back(o.id);
		optionLabels.push_back(o.text);
	}

	auto widget = std::make_shared<UIOptionListMorpher>(id, dropdownStyle, spinlistStyle);
	applyInputButtons(*widget, node["inputButtons"].asString("list"));
	widget->setOptions(optionIds, optionLabels);
	return widget;
}

std::shared_ptr<UIWidget> UIFactory::makeTreeList(const ConfigNode& entryNode)
{
	const auto& node = entryNode["widget"];
	auto id = node["id"].asString();
	auto style = UIStyle(node["style"].asString("list"), styleSheet);
	auto label = parseLabel(node);

	auto widget = std::make_shared<UITreeList>(id, style);
	applyInputButtons(*widget, node["inputButtons"].asString("treeList"));

	widget->setDragEnabled(node["canDrag"].asBool(false));

	return widget;
}

std::shared_ptr<UIWidget> UIFactory::makeDebugConsole(const ConfigNode& entryNode)
{
	const auto& node = entryNode["widget"];
	auto id = node["id"].asString();

	auto widget = std::make_shared<UIDebugConsole>(id, *this, std::make_shared<UIDebugConsoleController>());

	return widget;
}

bool UIFactory::hasCondition(const String& condition) const
{
	return std::find(conditions.begin(), conditions.end(), condition) != conditions.end();
}

bool UIFactory::resolveConditions(const ConfigNode& node) const
{
	auto resolveCondition = [&] (const String& cond) -> bool
	{
		if (cond.startsWith("!")) {
			return !hasCondition(cond.mid(1));
		} else {
			return hasCondition(cond);
		}
	};

	if (node.getType() == ConfigNodeType::Sequence) {
		bool ok = true;
		for (auto& c: node.asSequence()) {
			ok &= resolveCondition(c.asString());
		}
		return ok;
	} else {
		return resolveCondition(node.asString());
	}
}

Colour4f UIFactory::getColour(const String& key) const
{
	if (key.startsWith("#")) {
		return Colour4f::fromString(key);
	} else if (key.startsWith("$") && colourScheme) {
		return colourScheme->getColour(key.mid(1));
	} else {
		return Colour4f();
	}
}

void UIFactory::loadDefaultColourScheme()
{
	const String defaultColourScheme = "colour_schemes/default";
	if (resources.exists<ConfigFile>(defaultColourScheme)) {
		colourScheme = std::make_shared<UIColourScheme>(resources.get<ConfigFile>(defaultColourScheme)->getRoot(), resources);
	}
}
