Procházet zdrojové kódy

[filebrowser] Added gallery zoom on hover + alt

Clément Espeute před 2 měsíci
rodič
revize
ab53791c44
4 změnil soubory, kde provedl 271 přidání a 121 odebrání
  1. 38 12
      bin/style.css
  2. 110 86
      bin/style.less
  3. 84 23
      hide/comp/FancyGallery.hx
  4. 39 0
      hide/comp/FancyTooltip.hx

+ 38 - 12
bin/style.css

@@ -28,6 +28,7 @@ body {
   user-select: none;
   max-width: 100vw;
   max-height: 100vh;
+  overflow: hidden;
 }
 .lm_header .lm_tab {
   font-family: var(--default-font), Serif !important;
@@ -5205,7 +5206,8 @@ fancy-gallery fancy-scroll fancy-item-container {
   min-height: 100%;
   contain: layout paint size style;
 }
-fancy-gallery fancy-scroll fancy-item-container fancy-item {
+fancy-gallery fancy-item,
+fancy-tooltip fancy-item {
   padding: 4px;
   position: absolute;
   align-items: stretch;
@@ -5213,21 +5215,26 @@ fancy-gallery fancy-scroll fancy-item-container fancy-item {
   contain: layout paint size style;
   background-color: #282828;
   border-radius: 6px;
+  color: #aaa;
   box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.4);
 }
-fancy-gallery fancy-scroll fancy-item-container fancy-item:hover {
+fancy-gallery fancy-item:hover,
+fancy-tooltip fancy-item:hover {
   background-color: var(--hover);
 }
-fancy-gallery fancy-scroll fancy-item-container fancy-item.selected {
+fancy-gallery fancy-item.selected,
+fancy-tooltip fancy-item.selected {
   color: white;
   background-color: var(--selection);
 }
-fancy-gallery fancy-scroll fancy-item-container fancy-item.current {
+fancy-gallery fancy-item.current,
+fancy-tooltip fancy-item.current {
   outline: dashed 1px #AAA;
   z-index: 10;
   outline-offset: -1px;
 }
-fancy-gallery fancy-scroll fancy-item-container fancy-item.details {
+fancy-gallery fancy-item.details,
+fancy-tooltip fancy-item.details {
   padding: 0;
   display: flex;
   align-items: flex-end;
@@ -5235,28 +5242,33 @@ fancy-gallery fancy-scroll fancy-item-container fancy-item.details {
   border-radius: unset;
   gap: 0.2em;
 }
-fancy-gallery fancy-scroll fancy-item-container fancy-item fancy-thumbnail {
+fancy-gallery fancy-item fancy-thumbnail,
+fancy-tooltip fancy-item fancy-thumbnail {
   flex: 0 1;
   display: block;
   width: 100%;
   height: 100%;
 }
-fancy-gallery fancy-scroll fancy-item-container fancy-item .icon-placement {
+fancy-gallery fancy-item .icon-placement,
+fancy-tooltip fancy-item .icon-placement {
   position: absolute;
   color: white;
   bottom: 2px;
   right: 2px;
   filter: drop-shadow(2px 2px 2px rgba(0, 0, 0, 0.7));
 }
-fancy-gallery fancy-scroll fancy-item-container fancy-item .thumb {
+fancy-gallery fancy-item .thumb,
+fancy-tooltip fancy-item .thumb {
   transform: unset;
 }
-fancy-gallery fancy-scroll fancy-item-container fancy-item.details fancy-image {
+fancy-gallery fancy-item.details fancy-image,
+fancy-tooltip fancy-item.details fancy-image {
   height: 100%;
   width: auto;
   aspect-ratio: 1 / 1;
 }
-fancy-gallery fancy-scroll fancy-item-container fancy-item fancy-name {
+fancy-gallery fancy-item fancy-name,
+fancy-tooltip fancy-item fancy-name {
   position: absolute;
   bottom: 2px;
   left: 2px;
@@ -5269,12 +5281,14 @@ fancy-gallery fancy-scroll fancy-item-container fancy-item fancy-name {
   text-overflow: ellipsis;
   overflow: hidden;
 }
-fancy-gallery fancy-scroll fancy-item-container fancy-item.details fancy-name {
+fancy-gallery fancy-item.details fancy-name,
+fancy-tooltip fancy-item.details fancy-name {
   position: relative;
   height: auto;
   text-align: left;
 }
-fancy-gallery fancy-scroll fancy-item-container fancy-item.details .icon-placement {
+fancy-gallery fancy-item.details .icon-placement,
+fancy-tooltip fancy-item.details .icon-placement {
   position: relative;
   flex: 0 1;
   filter: unset;
@@ -5301,6 +5315,18 @@ fancy-closable {
 fancy-closable * {
   box-sizing: border-box;
 }
+fancy-tooltip {
+  z-index: 1;
+  position: absolute;
+  pointer-events: none;
+  border: none;
+  box-shadow: none;
+  outline: none;
+  background: none;
+  box-shadow: var(--basic-shadow);
+  margin: 0;
+  overflow: visible;
+}
 .ref-viewer {
   overflow-x: hidden;
 }

+ 110 - 86
bin/style.less

@@ -34,6 +34,8 @@ body {
 
 	max-width: 100vw;
 	max-height: 100vh;
+
+	overflow: hidden;
 }
 
 .lm_header .lm_tab {
@@ -6240,111 +6242,113 @@ fancy-gallery {
 			min-height: 100%;
 
 			contain: layout paint size style;
+		}
+	}
+}
 
-			fancy-item {
-				padding: 4px;
-				position: absolute;
-				align-items: stretch;
-				overflow: hidden;
+fancy-gallery fancy-item, fancy-tooltip fancy-item {
+	padding: 4px;
+	position: absolute;
+	align-items: stretch;
+	overflow: hidden;
 
-				contain: layout paint size style;
+	contain: layout paint size style;
 
-				background-color: #282828;
-				border-radius: 6px;
+	background-color: #282828;
+	border-radius: 6px;
 
-				&:hover {
-					background-color: var(--hover);
-				}
+	color: #aaa;
 
-				&.selected {
-					color: white;
-					background-color: var(--selection);
-				}
+	&:hover {
+		background-color: var(--hover);
+	}
 
-				&.current {
-					outline: dashed 1px #AAA;
-					z-index: 10;
-					outline-offset: -1px;
-				}
+	&.selected {
+		color: white;
+		background-color: var(--selection);
+	}
 
-				box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.4);
+	&.current {
+		outline: dashed 1px #AAA;
+		z-index: 10;
+		outline-offset: -1px;
+	}
 
-				&.details {
-					padding: 0;
-					display: flex;
-					align-items: flex-end;
+	box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.4);
 
-					box-shadow: unset;
-					border-radius: unset;
+	&.details {
+		padding: 0;
+		display: flex;
+		align-items: flex-end;
 
-					gap: 0.2em;
+		box-shadow: unset;
+		border-radius: unset;
 
-				}
+		gap: 0.2em;
 
-				fancy-thumbnail {
-					flex: 0 1;
-					display: block;
-					width: 100%;
-					height: 100%;
-				}
+	}
 
-				.icon-placement {
-					position: absolute;
-					color: white;
-					bottom: 2px;
-					right: 2px;
-					filter: drop-shadow(2px 2px 2px rgb(0 0 0 / 70%));
-				}
+	fancy-thumbnail {
+		flex: 0 1;
+		display: block;
+		width: 100%;
+		height: 100%;
+	}
 
-				.thumb {
-					transform: unset;
-				}
+	.icon-placement {
+		position: absolute;
+		color: white;
+		bottom: 2px;
+		right: 2px;
+		filter: drop-shadow(2px 2px 2px rgb(0 0 0 / 70%));
+	}
 
-				// .loading {
-				// 	// content: strict;
-				// 	// animation: spinner 5.0s linear infinite;
-				// 	// transform: rotate(0deg);
-				// 	// @keyframes spinner {
-				// 	// 	to { transform: rotate(360deg); }
-				// 	// }
-				// }
+	.thumb {
+		transform: unset;
+	}
 
-				&.details fancy-image {
-					height: 100%;
-					width: auto;
-					aspect-ratio: 1 / 1;
-				}
+	// .loading {
+	// 	// content: strict;
+	// 	// animation: spinner 5.0s linear infinite;
+	// 	// transform: rotate(0deg);
+	// 	// @keyframes spinner {
+	// 	// 	to { transform: rotate(360deg); }
+	// 	// }
+	// }
 
-				fancy-name {
-					position: absolute;
-					bottom: 2px;
-					left: 2px;
-					right: 2px;
-					height: 32px;
-					display: block;
-					text-align: center;
-					text-wrap: wrap;
-					word-break: break-all;
-					text-overflow: ellipsis;
-					overflow: hidden;
-				}
+	&.details fancy-image {
+		height: 100%;
+		width: auto;
+		aspect-ratio: 1 / 1;
+	}
 
-				&.details fancy-name {
-					position: relative;
-					height: auto;
-					text-align: left;
-				}
+	fancy-name {
+		position: absolute;
+		bottom: 2px;
+		left: 2px;
+		right: 2px;
+		height: 32px;
+		display: block;
+		text-align: center;
+		text-wrap: wrap;
+		word-break: break-all;
+		text-overflow: ellipsis;
+		overflow: hidden;
+	}
 
-				&.details .icon-placement {
-					position: relative;
-					flex: 0 1;
-					filter: unset;
-					color : #AAA;
-					margin-left: 0.2em;
-					font-size: 0.8em;
-				}
-			}
-		}
+	&.details fancy-name {
+		position: relative;
+		height: auto;
+		text-align: left;
+	}
+
+	&.details .icon-placement {
+		position: relative;
+		flex: 0 1;
+		filter: unset;
+		color : #AAA;
+		margin-left: 0.2em;
+		font-size: 0.8em;
 	}
 }
 
@@ -6372,6 +6376,26 @@ fancy-closable {
 	height: 0px;
 }
 
+fancy-tooltip {
+	z-index: 1;
+	position: absolute;
+	pointer-events: none;
+
+	border: none;
+	box-shadow: none;
+	outline: none;
+	background: none;
+
+	box-shadow: var(--basic-shadow);
+
+	margin: 0;
+	overflow: visible;
+
+	// fancy-image {
+	// 	transform: none;
+	// }
+}
+
 .ref-viewer {
 	overflow-x: hidden;
 	>.header {

+ 84 - 23
hide/comp/FancyGallery.hx

@@ -19,13 +19,18 @@ class FancyGallery<GalleryItem> extends hide.comp.Component {
 	var scroll : js.html.Element;
 	var selection : Map<{}, Bool> = [];
 
+	var tooltip : FancyTooltip;
+
 	var lastHeight : Float = 0;
+	var mouseOver : Bool = false;
 
 	var zoom : Int = 5;
 	static final zoomLevels = [0, 32, 64, 96, 128,192, 256, 384, 512];
 	var itemHeightPx = 128;
 	var itemWidthPx = 128;
 	static final itemTitleHeight = 32;
+	static final zoomedThumbnailSize = 512;
+	static final zoomedThumbnailMargin = 32;
 
 	var details = false;
 
@@ -66,6 +71,8 @@ class FancyGallery<GalleryItem> extends hide.comp.Component {
 			</fancy-scroll>
 		");
 
+		tooltip = new FancyTooltip();
+
 		zoom = getDisplayState("zoom") ?? zoom;
 
 		var htmlElem = el.get(0);
@@ -73,6 +80,37 @@ class FancyGallery<GalleryItem> extends hide.comp.Component {
 
 		htmlElem.onkeydown = inputHandler;
 
+		htmlElem.onmousemove = (e:js.html.MouseEvent) -> {
+			var x = if (details) e.clientX + zoomedThumbnailMargin else e.clientX - Std.int(zoomedThumbnailSize/2);
+			tooltip.x = hxd.Math.iclamp(x, zoomedThumbnailMargin, Std.int(js.Browser.window.innerWidth) - zoomedThumbnailSize - zoomedThumbnailMargin);
+			tooltip.y = hxd.Math.iclamp(e.clientY - Std.int(zoomedThumbnailSize/2), zoomedThumbnailMargin, Std.int(js.Browser.window.innerHeight) - zoomedThumbnailSize - itemTitleHeight - zoomedThumbnailMargin);
+			if (e.altKey) {
+				tooltip.show();
+			} else {
+				tooltip.hide();
+			}
+		};
+
+		htmlElem.onmouseenter = (e:js.html.MouseEvent) -> {
+			mouseOver = true;
+		}
+
+		htmlElem.onmouseleave = (e:js.html.MouseEvent) -> {
+			mouseOver = false;
+			tooltip.hide();
+		}
+
+		js.Browser.document.addEventListener("keydown", (e: js.html.KeyboardEvent) -> {
+			if (mouseOver && e.key == "Alt") {
+				tooltip.show();
+			}
+		});
+		js.Browser.document.addEventListener("keyup", (e: js.html.KeyboardEvent) -> {
+			if (e.key == "Alt") {
+				tooltip.hide();
+			}
+		});
+
 		var resizeObserver = new hide.comp.ResizeObserver((_, _) -> {
 			queueRefresh();
 		});
@@ -386,68 +424,91 @@ class FancyGallery<GalleryItem> extends hide.comp.Component {
 		}
 	}
 
-	function getElement(data : GalleryItemData<GalleryItem>) : js.html.Element {
-		if (currentRefreshFlags.has(RegenHeader) && data.element != null) {
+	function getElement(data : GalleryItemData<GalleryItem>, tooltip = false) : js.html.Element {
+		if (!tooltip && currentRefreshFlags.has(RegenHeader) && data.element != null) {
 			data.element.remove();
 			data.element = null;
 		}
 
-		if (data.element == null) {
-			data.element = js.Browser.document.createElement("fancy-item");
-			untyped data.element.__data = data;
+		var genElement = !tooltip ? data.element : null;
+		if (genElement == null) {
+			genElement = js.Browser.document.createElement("fancy-item");
+			if (!tooltip)
+				data.element = genElement;
+			untyped genElement.__data = data;
 			data.thumbnailStringCache = null;
 			data.iconStringCache = null;
 
-			data.element.innerHTML = '
+			genElement.innerHTML = '
 				<fancy-thumbnail></fancy-thumbnail>
 				<fancy-name></fancy-name>
 				<div class="icon-placement"></div>
 			';
 
-			data.element.ondblclick = (e) -> {
-				onDoubleClick(data.item);
-			}
+			if (!tooltip) {
+				genElement.ondblclick = (e) -> {
+					onDoubleClick(data.item);
+				}
+
+				genElement.onclick = dataClickHandler.bind(data);
+
+				genElement.oncontextmenu = contextMenuHandler.bind(data.item);
 
-			data.element.onclick = dataClickHandler.bind(data);
+				genElement.onmouseover = (e: js.html.MouseEvent) -> {
+					var child = getElement(data, true);
+					this.tooltip.element.empty();
+					this.tooltip.element.append(child);
+				}
 
-			data.element.oncontextmenu = contextMenuHandler.bind(data.item);
+				setupDragAndDrop(data);
+			}
+		}
 
-			setupDragAndDrop(data);
+		var details = this.details;
+		var itemWidthPx = this.itemWidthPx;
+		var itemHeightPx = this.itemHeightPx;
+		if (tooltip) {
+			itemWidthPx = zoomedThumbnailSize;
+			itemHeightPx = zoomedThumbnailSize + itemTitleHeight;
+			details = false;
 		}
 
 		if (!details) {
-			data.element.style.width = '${itemWidthPx}px';
-			data.element.style.height = '${itemHeightPx}px';
+			genElement.style.width = '${itemWidthPx}px';
+			genElement.style.height = '${itemHeightPx}px';
 		} else {
-			data.element.style.width = '100%';
+			genElement.style.width = '100%';
 		}
 
-		data.element.style.height = '${itemHeightPx}px';
-		data.element.classList.toggle("details", details);
-		data.element.classList.toggle("selected", selection.exists(cast data));
-		data.element.classList.toggle("current", currentVisible && currentItem == data);
+		genElement.style.height = '${itemHeightPx}px';
 
-		var name = data.element.querySelector("fancy-name");
+		if (!tooltip) {
+			genElement.classList.toggle("details", details);
+			genElement.classList.toggle("selected", selection.exists(cast data));
+			genElement.classList.toggle("current", currentVisible && currentItem == data);
+		}
+
+		var name = genElement.querySelector("fancy-name");
 		if (name.title != data.name) {
 			name.innerHTML = '<span class="bg">${data.name}</span>';
 			name.title = data.name;
 		}
 
-		var img = data.element.querySelector("fancy-thumbnail");
+		var img = genElement.querySelector("fancy-thumbnail");
 		var imgString = getThumbnail(data.item) ?? '<fancy-image style="background-image:url(\'res/icons/svg/unknown_file.svg\')"></fancy-image>';
 		if (imgString != data.thumbnailStringCache) {
 			img.innerHTML = imgString;
 			data.thumbnailStringCache = imgString;
 		}
 
-		var icon = data.element.querySelector(".icon-placement");
+		var icon = genElement.querySelector(".icon-placement");
 		var iconString = getIcon(data.item);
 		if (iconString != data.iconStringCache) {
 			icon.innerHTML = iconString;
 			data.iconStringCache = iconString;
 		}
 
-		return data.element;
+		return genElement;
 	}
 
 	function rebuildItems() {

+ 39 - 0
hide/comp/FancyTooltip.hx

@@ -0,0 +1,39 @@
+package hide.comp;
+
+class FancyTooltip extends hide.comp.Component {
+	var htmlElem : js.html.Element;
+
+	public var x(default, set): Int = 0;
+	function set_x(v: Int) {
+		x = v;
+		htmlElem.style.left = '${x}px';
+		return v;
+	}
+
+	public var y(default, set): Int = 0;
+	function set_y(v: Int) {
+		y = v;
+		htmlElem.style.top = '${y}px';
+		return v;
+	}
+
+	public function new(parent: hide.Element = null, el: hide.Element = null) {
+		if (parent == null) {
+			parent = new hide.Element("body");
+		}
+		if (el == null) {
+			el = new hide.Element("<fancy-tooltip></fancy-tooltip>");
+		}
+		super(parent, el);
+		htmlElem = element.get(0);
+		untyped htmlElem.popover = "manual";
+	}
+
+	public function show() {
+      untyped htmlElem.showPopover();
+	}
+
+	public function hide() {
+      untyped htmlElem.hidePopover();
+	}
+}