Răsfoiți Sursa

FileBrowser: add favorites

LeoVgr 3 săptămâni în urmă
părinte
comite
24b37c6126

+ 0 - 2
bin/res/icons/svg/generate.hx

@@ -1,2 +0,0 @@
-
-

+ 0 - 0
bin/res/icons/svg/icons.css


+ 0 - 0
bin/res/icons/svg/icons.less


+ 37 - 1
bin/style.css

@@ -4893,10 +4893,38 @@ file-browser.vertical {
 file-browser.single > .left {
   flex: 1 1;
 }
+file-browser fancy-scroll {
+  display: block;
+  width: 100%;
+  flex: 1 1;
+  min-width: 100px;
+  overflow-x: clip;
+  overflow-y: auto;
+}
+file-browser fancy-scroll fancy-item-container {
+  position: relative;
+  display: block;
+  overflow: hidden;
+  contain: strict;
+}
 file-browser .left {
   width: 300px;
   min-width: 100px;
-  background-color: mediumaquamarine;
+  background-color: #222222;
+  display: flex;
+  flex-direction: column;
+}
+file-browser .left .top {
+  width: 100%;
+  padding-bottom: 20px;
+}
+file-browser .left .top.hidden {
+  height: 0px;
+  padding-bottom: 0px;
+}
+file-browser .left .bot {
+  width: 100%;
+  flex: 1;
 }
 file-browser .right {
   flex: 1 1;
@@ -5244,6 +5272,14 @@ fancy-tooltip {
   mask-image: url("res/icons/svg/dot.svg");
   color: #FE383A;
 }
+.fancy-status-icon.fancy-status-icon-star::after {
+  width: 11px;
+  height: 11px;
+  left: -4px;
+  bottom: -1px;
+  mask-image: url("res/icons/svg/star.svg");
+  color: #fcbf07;
+}
 .lm_tabdropdown_list {
   z-index: 9999 !important;
   background-color: #111111;

+ 44 - 1
bin/style.less

@@ -5838,10 +5838,44 @@ file-browser {
 	height: 100%;
 	width: 100%;
 
+	fancy-scroll {
+		display: block;
+		width: 100%;
+		flex: 1 1;
+		min-width: 100px;
+		overflow-x: clip;
+		overflow-y: auto;
+
+		fancy-item-container {
+			position: relative;
+			display: block;
+			overflow: hidden;
+
+			contain: strict;
+		}
+
+	}
+
 	.left {
 		width: 300px;
 		min-width: 100px;
-		background-color: mediumaquamarine;
+		background-color: #222222;
+		display: flex;
+		flex-direction: column;
+
+		.top {
+			&.hidden {
+				height: 0px;
+				padding-bottom: 0px;
+			}
+			width: 100%;
+			padding-bottom: 20px;
+		}
+
+		.bot {
+			width: 100%;
+			flex: 1;
+		}
 	}
 
 	.right {
@@ -6280,6 +6314,15 @@ fancy-tooltip {
 		mask-image: url("res/icons/svg/dot.svg");
 		color: #FE383A;
 	}
+
+	&.fancy-status-icon-star::after {
+		width: 11px;
+		height: 11px;
+		left: -4px;
+		bottom: -1px;
+		mask-image: url("res/icons/svg/star.svg");
+		color: #fcbf07;
+	}
 }
 
 .lm_tabdropdown_list {

+ 34 - 16
hide/comp/FancyTree.hx

@@ -68,7 +68,9 @@ typedef TreeButton<TreeItem> = {
 
 typedef Params =  {
 	?saveDisplayKey : String,
-	?quickGoto : Bool
+	?quickGoto : Bool,
+	?search : Bool,
+	?customScroll : js.html.Element
 }
 
 class FancyTree<TreeItem> extends hide.comp.Component {
@@ -83,6 +85,8 @@ class FancyTree<TreeItem> extends hide.comp.Component {
 	var gotoFileCurrentMatch = "";
 
 	var quickGoto : Bool = true;
+	var allowSearch : Bool = true;
+	var useCustomScroll : Bool = false;
 
 	static final gotoFileKeyMaxDelay = 0.5;
 	static final overDragOpenDelaySec = 0.5;
@@ -118,6 +122,8 @@ class FancyTree<TreeItem> extends hide.comp.Component {
 	public function new(parent: Element, ?params: Params) {
 		saveDisplayKey = params?.saveDisplayKey;
 		quickGoto = params?.quickGoto != null ? params.quickGoto : true;
+		allowSearch = params?.search;
+		useCustomScroll = params?.customScroll != null;
 
 		var el = new Element('
 			<fancy-tree tabindex="-1">
@@ -132,19 +138,21 @@ class FancyTree<TreeItem> extends hide.comp.Component {
 
 		loadState();
 
-		searchBarClosable = new FancyClosable(null, element.find("fancy-closable"));
+		if (allowSearch) {
+			searchBarClosable = new FancyClosable(null, element.find("fancy-closable"));
 
-		searchBar = new FancySearch(null, element.find("fancy-search"));
-		searchBar.onSearch = (search, _) -> {
-			currentSearch = search.toLowerCase();
-			queueRefresh(Search);
-		}
+			searchBar = new FancySearch(null, element.find("fancy-search"));
+			searchBar.onSearch = (search, _) -> {
+				currentSearch = search.toLowerCase();
+				queueRefresh(Search);
+			}
 
-		searchBarClosable.onClose = () -> {
-			onSearchClose();
+			searchBarClosable.onClose = () -> {
+				onSearchClose();
+			}
 		}
 
-		scroll = el.find("fancy-scroll").get(0);
+		scroll = params.customScroll == null ? el.find("fancy-scroll").get(0) : params.customScroll;
 		itemContainer = el.find("fancy-item-container").get(0);
 		lastHeight = null;
 
@@ -221,6 +229,12 @@ class FancyTree<TreeItem> extends hide.comp.Component {
 		return [];
 	}
 
+	public dynamic function onSearch() {
+	}
+
+	public dynamic function onSearchEnd() {
+	}
+
 	/**
 		Drag and drop interface.
 		Set this struct with all of it's function callback to handle drag and drop inside your tree.
@@ -442,10 +456,14 @@ class FancyTree<TreeItem> extends hide.comp.Component {
 	}
 
 	function inputHandler(e: js.html.KeyboardEvent) {
+		if (!allowSearch)
+			return;
+
 		if (hide.ui.Keys.matchJsEvent("search", e, ide.currentConfig)) {
 			e.stopPropagation();
 			e.preventDefault();
 
+			onSearch();
 			searchBarClosable.toggleOpen(true);
 			searchBar.focus();
 			currentSearch = searchBar.getValue().toLowerCase();
@@ -467,6 +485,7 @@ class FancyTree<TreeItem> extends hide.comp.Component {
 				e.stopPropagation();
 				e.preventDefault();
 
+				onSearchEnd();
 				searchBarClosable.toggleOpen(false);
 				searchBar.blur();
 
@@ -525,7 +544,7 @@ class FancyTree<TreeItem> extends hide.comp.Component {
 			return;
 		}
 
-		if (currentItem == null || searchBar.hasFocus())
+		if (currentItem == null || searchBar?.hasFocus())
 			return;
 
 		if (e.key == "ArrowRight" && hasChildren(currentItem.item)) {
@@ -628,7 +647,8 @@ class FancyTree<TreeItem> extends hide.comp.Component {
 			regenerateFlatData();
 		}
 
-		//itemContainer.innerHTML = "";
+		var scrollHeight = scroll.getBoundingClientRect().height;
+		var offsetTop = useCustomScroll ? scroll.getElementsByClassName("top")[0].getBoundingClientRect().height : 0;
 		var oldChildren = [for (node in itemContainer.childNodes) node];
 
 		var itemHeightPx = 20;
@@ -639,13 +659,11 @@ class FancyTree<TreeItem> extends hide.comp.Component {
 			lastHeight = height;
 		}
 
-		var scrollHeight = scroll.getBoundingClientRect().height;
-
 		if (currentRefreshFlags.has(FocusCurrent)) {
 			var currentIndex = flatData.indexOf(currentItem);
 
 			if (currentIndex >= 0) {
-				var currentHeight = currentIndex * itemHeightPx;
+				var currentHeight = currentIndex * itemHeightPx - offsetTop;
 				if (currentHeight < scroll.scrollTop) {
 					scroll.scrollTo(scroll.scrollLeft, currentHeight);
 				}
@@ -656,7 +674,7 @@ class FancyTree<TreeItem> extends hide.comp.Component {
 			}
 		}
 
-		var clipStart = scroll.scrollTop;
+		var clipStart = scroll.scrollTop - offsetTop;
 		var clipEnd = scrollHeight + clipStart;
 		var itemStart = hxd.Math.floor(clipStart / itemHeightPx);
 		var itemEnd = hxd.Math.ceil(clipEnd / itemHeightPx);

+ 1 - 1
hide/comp/SceneEditor.hx

@@ -2065,7 +2065,7 @@ class SceneEditor {
 			icons.set(f, Reflect.field(iconsConfig,f));
 
 		var saveDisplayKey = isSceneTree ? view.saveDisplayKey + '/tree' : view.saveDisplayKey + '/renderPropsTree';
-		var tree = new FancyTree<hrt.prefab.Prefab>(null, { saveDisplayKey: saveDisplayKey, quickGoto: false });
+		var tree = new FancyTree<hrt.prefab.Prefab>(null, { saveDisplayKey: saveDisplayKey, quickGoto: false, search: true });
 		tree.getChildren = (p : hrt.prefab.Prefab) -> {
 			if (p == null) {
 				if (isSceneTree)

+ 226 - 3
hide/view/FileBrowser.hx

@@ -2,6 +2,13 @@ package hide.view;
 
 import hide.tools.FileManager;
 import hide.tools.FileManager.FileEntry;
+
+typedef FavoriteEntry = {
+	parent : FavoriteEntry,
+	children : Array<FavoriteEntry>,
+	?ref : FileEntry,
+}
+
 typedef FileBrowserState = {
 	savedLayout: Layout,
 }
@@ -14,6 +21,7 @@ enum abstract Layout(String) {
 }
 
 class FileBrowser extends hide.ui.View<FileBrowserState> {
+	static var FAVORITES_KEY = "filebrowser_favorites";
 
 	var fileTree: Element;
 	var fileIcons: Element;
@@ -46,6 +54,10 @@ class FileBrowser extends hide.ui.View<FileBrowserState> {
 	override function new(state) {
 		super(state);
 		saveDisplayKey = "fileBrowser";
+
+		this.favorites = getDisplayState(FAVORITES_KEY);
+		if (this.favorites == null)
+			this.favorites = [];
 	}
 
 	override function buildTabMenu():Array<hide.comp.ContextMenu.MenuItem> {
@@ -134,6 +146,9 @@ class FileBrowser extends hide.ui.View<FileBrowserState> {
 	var statFileCount: Int = 0;
 	var statFileFiltered: Int = 0;
 
+	var favorites : Array<String>;
+	var favoritesTree : hide.comp.FancyTree<FavoriteEntry>;
+
 	function set_filterEnabled(v : Bool) {
 		var anySet = false;
 		for (key => value in filterState) {
@@ -355,7 +370,12 @@ class FileBrowser extends hide.ui.View<FileBrowserState> {
 
 		var browserLayout = new Element('
 			<file-browser>
-				<div class="left"></div>
+				<div class="left">
+					<fancy-scroll>
+						<div class="top"></div>
+						<div class="bot"></div>
+					</fancy-scroll>
+				</div>
 				<div class="right" tabindex="-1">
 					<fancy-toolbar class="fancy-small shadow">
 						<fancy-button class="btn-parent quiet" title="Go to parent folder">
@@ -413,7 +433,182 @@ class FileBrowser extends hide.ui.View<FileBrowserState> {
 			}
 		}
 
-		fancyTree = new hide.comp.FancyTree<FileEntry>(browserLayout.find(".left"), { saveDisplayKey: "fileBrowserTree" } );
+		// Favorites tree
+		favoritesTree = new hide.comp.FancyTree<FavoriteEntry>(browserLayout.find(".left").find(".top"), { saveDisplayKey: "fileBrowserTree_Favorites" } );
+		favoritesTree.getChildren = (file: FavoriteEntry) -> {
+			function rec(parent : FavoriteEntry) {
+				if (parent.ref.children == null)
+					return;
+				for (c in parent.ref.children) {
+					var f = { parent : parent, children : [], ref: FileManager.inst.getFileEntry(c.path) };
+					parent.children.push(f);
+					rec(f);
+				}
+			}
+
+			if (file == null) {
+				var fav : FavoriteEntry = {
+					parent : null,
+					children : [],
+					ref : null
+				}
+
+				fav.children = [];
+				for (f in favorites) {
+					// Ref could be null if this f is a favorite of another project
+					var ref = FileManager.inst.getFileEntry(f);
+					if (ref == null)
+						continue;
+					var c = { parent: fav, children: [], ref: ref }
+					fav.children.push(c);
+					rec(c);
+				}
+
+				return [fav];
+			}
+
+			if (file?.ref?.kind == File)
+				return null;
+			if (file.children == null)
+				throw "null children";
+
+			return file.children;
+
+			// return [for (c in file.children) { parent: file, children: [], ref: FileManager.inst.getFileEntry(c.ref.path) }];
+		};
+		favoritesTree.getName = (file: FavoriteEntry) -> return file.ref == null ? "Favorites" : file?.ref.name;
+		favoritesTree.getUniqueName = (file: FavoriteEntry) -> {
+			if (file == null)
+				return "";
+			if (file.ref == null)
+				return "favorites";
+			var relPath = file.ref.name;
+			var p = file.parent;
+			while (p != null) {
+				var name = p.ref == null ? "favorites" : p.ref.name;
+				relPath = name + "/" + relPath;
+				p = p.parent;
+			}
+			return relPath;
+		}
+		favoritesTree.getIcon = (file: FavoriteEntry) -> {
+			if (file.parent == null)
+				return '<div class="ico ico-star"></div>';
+
+			var fav = file.parent.ref == null ? "fancy-status-icon fancy-status-icon-star" : "";
+			if (file.ref.kind == Dir)
+				return '<div class="ico ico-folder $fav"></div>';
+			var ext = Extension.getExtension(file.ref.name);
+			if (ext != null) {
+				if (ext?.options.icon != null) {
+					return '<div class="ico ico-${ext.options.icon} $fav" title="${ext.options.name ?? "Unknown"}"></div>';
+				}
+			}
+			return '<div class="ico ico-file $fav" title="Unknown"></div>';
+		};
+		favoritesTree.onContextMenu = (item: FavoriteEntry, event: js.html.MouseEvent) -> {
+			event.stopPropagation();
+			event.preventDefault();
+
+			var options : Array<hide.comp.ContextMenu.MenuItem> = [];
+			options.push({
+				label: "Collapse",
+				click: () -> {
+					var collapseTarget = item;
+					if (item.ref.kind != Dir)
+						collapseTarget = item.parent;
+					favoritesTree.collapseItem(collapseTarget);
+				}
+			});
+			options.push({
+				label: "Collapse All",
+				click: () -> {
+					for (child in item.children)
+						favoritesTree.collapseItem(child);
+				}
+			});
+			options.push({
+				isSeparator: true
+			});
+
+			// Root favorite tree options
+			var isFavoriteRoot = item?.parent == null;
+			if (isFavoriteRoot) {
+				options.push({
+					label: "Clear Favorites",
+					click: () -> {
+						favorites = [];
+						saveDisplayState(FAVORITES_KEY, favorites);
+						favoritesTree.rebuildTree();
+						this.favoritesTree.element.parent().hide();
+					}
+				});
+
+				hide.comp.ContextMenu.createFromEvent(event, options);
+				return;
+			}
+			else {
+				if (!this.favorites.contains(item.ref.getPath())) {
+				options.push({ label: "Mark as Favorite", click : function() {
+					this.favorites.push(item.ref.getPath());
+					saveDisplayState(FAVORITES_KEY, this.favorites);
+					this.favoritesTree.rebuildTree();
+					this.favoritesTree.element.parent().show();
+				}});
+				}
+				else {
+					options.push({ label: "Remove from Favorite", click : function() {
+						this.favorites.remove(item.ref.getPath());
+						saveDisplayState(FAVORITES_KEY, this.favorites);
+						this.favoritesTree.rebuildTree();
+						if (this.favorites.length == 0)
+							this.favoritesTree.element.parent().hide();
+					}});
+				}
+			}
+
+			hide.comp.ContextMenu.createFromEvent(event, options);
+		};
+		favoritesTree.onDoubleClick = (item: FavoriteEntry) -> {
+			if (item?.ref?.kind == File)
+				ide.openFile(item.ref.getPath());
+			else
+				favoritesTree.openItem(item);
+		}
+		favoritesTree.onSelectionChanged = (enterKey) -> {
+			fancyTree.clearSelection();
+
+			var selection = favoritesTree.getSelectedItems();
+
+			// Sinc folder view with other filebrowser in SingleMiniature mode
+			if (selection.length > 0) {
+				if (selection[0].ref == null) return;
+
+				openDir(selection[0].ref, false);
+				var views = ide.getViews(hide.view.FileBrowser);
+				for (view in views) {
+					if (view == this)
+						continue;
+					if (view.layout == SingleMiniature) {
+						view.openDir(selection[0].ref, false);
+					}
+				}
+			}
+
+			if (enterKey) {
+				if (selection[0].ref.kind == File) {
+					ide.openFile(selection[0].ref.getPath());
+				}
+			}
+		}
+
+		favoritesTree.rebuildTree();
+
+		if (this.favorites.length == 0)
+			this.favoritesTree.element.parent().hide();
+			
+		// Ressources tree
+		fancyTree = new hide.comp.FancyTree<FileEntry>(browserLayout.find(".left").find(".bot"), { saveDisplayKey: "fileBrowserTree_Main", search: true, customScroll: element.find("fancy-scroll").get(0) } );
 		fancyTree.getChildren = (file: FileEntry) -> {
 			if (file == null)
 				return [root];
@@ -437,6 +632,15 @@ class FileBrowser extends hide.ui.View<FileBrowserState> {
 
 		fancyTree.onNameChange = renameHandler;
 
+		fancyTree.onSearch = () -> {
+			favoritesTree.element.parent().hide();
+		}
+
+		fancyTree.onSearchEnd = () -> {
+			if (this.favorites.length > 0)
+				favoritesTree.element.parent().show();
+		}
+
 		fancyTree.dragAndDropInterface =
 		{
 			onDragStart: function(file: FileEntry, e: hide.tools.DragAndDrop.DragData) : Bool {
@@ -526,7 +730,6 @@ class FileBrowser extends hide.ui.View<FileBrowserState> {
 		fancyGallery.getThumbnail = (item : FileEntry) -> {
 			if (item.kind == Dir) {
 				return '<fancy-image style="background-image:url(\'res/icons/svg/big_folder.svg\')"></fancy-image>';
-
 			}
 			else if (item.iconPath == "loading") {
 				return '<fancy-image class="loading" style="background-image:url(\'res/icons/loading.gif\')"></fancy-image>';
@@ -585,6 +788,8 @@ class FileBrowser extends hide.ui.View<FileBrowserState> {
 
 
 		fancyTree.onSelectionChanged = (enterKey) -> {
+			favoritesTree.clearSelection();
+
 			var selection = fancyTree.getSelectedItems();
 
 			// Sinc folder view with other filebrowser in SingleMiniature mode
@@ -997,6 +1202,24 @@ class FileBrowser extends hide.ui.View<FileBrowserState> {
 			}});
 		}
 
+		if (!this.favorites.contains(item.getPath())) {
+			options.push({ label: "Mark as Favorite", click : function() {
+				this.favorites.push(item.getPath());
+				saveDisplayState(FAVORITES_KEY, this.favorites);
+				this.favoritesTree.rebuildTree();
+				this.favoritesTree.element.parent().show();
+			}});
+		}
+		else {
+			options.push({ label: "Remove from Favorite", click : function() {
+				this.favorites.remove(item.getPath());
+				saveDisplayState(FAVORITES_KEY, this.favorites);
+				this.favoritesTree.rebuildTree();
+				if (this.favorites.length == 0)
+					this.favoritesTree.element.parent().hide();
+			}});
+		}
+
 		options.push({ label: "Refresh Thumbnail(s)", click : function() {
 			var files = FileManager.inst.getRoots(getItemAndSelection(item, isGallery));
 			for (file in files) {