From 8fb58815b9b5acb98f5ab4dad95b45234fa1619d Mon Sep 17 00:00:00 2001 From: Lucas Dower Date: Mon, 26 Jun 2023 14:20:33 +0100 Subject: [PATCH] * Added tooltips to toolbar buttons * Refactored component styles for easier use with CSS selectors --- src/ui/components/file_input.ts | 15 ++++-- src/ui/components/header.ts | 9 ++-- src/ui/components/toolbar_item.ts | 20 ++++++- src/ui/layout.ts | 42 ++++++++++----- src/util/ui_util.ts | 31 +++++------ styles.css | 88 +++++++++++++++++++++++++------ 6 files changed, 147 insertions(+), 58 deletions(-) diff --git a/src/ui/components/file_input.ts b/src/ui/components/file_input.ts index a2d36e8..67acd4d 100644 --- a/src/ui/components/file_input.ts +++ b/src/ui/components/file_input.ts @@ -3,20 +3,21 @@ import * as path from 'path'; import { ASSERT } from '../../util/error_util'; import { UIUtil } from '../../util/ui_util'; import { ConfigComponent } from './config'; +import { AppIcons } from '../icons'; export class FileComponent extends ConfigComponent { - private _loadedFilePath: string; + private _loadedFilePath: string | null; public constructor() { super(); - this._loadedFilePath = ''; + this._loadedFilePath = null; } protected override _generateInnerHTML() { return `
- ${this._loadedFilePath} + ${this._loadedFilePath ?? 'No file chosen'}
`; } @@ -62,8 +63,12 @@ export class FileComponent extends ConfigComponent { } protected override _updateStyles() { - const parsedPath = path.parse(this._loadedFilePath); - this._getElement().innerHTML = parsedPath.name + parsedPath.ext; + if (this._loadedFilePath) { + const parsedPath = path.parse(this._loadedFilePath); + this._getElement().innerHTML = parsedPath.name + parsedPath.ext; + } else { + this._getElement().innerHTML = 'No file chosen'; + } UIUtil.updateStyles(this._getElement(), { isHovered: this.hovered, diff --git a/src/ui/components/header.ts b/src/ui/components/header.ts index e7c9b48..2aa0484 100644 --- a/src/ui/components/header.ts +++ b/src/ui/components/header.ts @@ -22,17 +22,20 @@ export class HeaderComponent extends BaseComponent { this._githubButton = new ToolbarItemComponent({ id: 'gh', iconSVG: AppIcons.GITHUB }) .onClick(() => { window.open('https://github.com/LucasDower/ObjToSchematic'); - }); + }) + .setTooltip('Open GitHub repo'); this._bugButton = new ToolbarItemComponent({ id: 'bug', iconSVG: AppIcons.BUG }) .onClick(() => { window.open('https://github.com/LucasDower/ObjToSchematic/issues'); - }); + }) + .setTooltip('Open GitHub issues'); this._discordButton = new ToolbarItemComponent({ id: 'disc', iconSVG: AppIcons.DISCORD }) .onClick(() => { window.open('https://discord.gg/McS2VrBZPD'); - }); + }) + .setTooltip('Open Discord server'); } // Header element shouldn't be diff --git a/src/ui/components/toolbar_item.ts b/src/ui/components/toolbar_item.ts index 2e2f383..4ea8dd6 100644 --- a/src/ui/components/toolbar_item.ts +++ b/src/ui/components/toolbar_item.ts @@ -12,6 +12,7 @@ export type TToolbarItemParams = { export class ToolbarItemComponent extends BaseComponent { private _iconSVG: SVGSVGElement; private _label: string; + private _tooltip: string | null; private _onClick?: () => void; private _isActive: boolean; private _grow: boolean; @@ -33,6 +34,7 @@ export class ToolbarItemComponent extends BaseComponent { } this._label = ''; + this._tooltip = null; } public setGrow() { @@ -90,6 +92,11 @@ export class ToolbarItemComponent extends BaseComponent { return this; } + public setTooltip(text: string) { + this._tooltip = text; + return this; + } + public generateHTML() { if (this._grow) { return ` @@ -98,11 +105,20 @@ export class ToolbarItemComponent extends BaseComponent { `; } else { - return ` -
+ if (this._tooltip === null) { + return ` +
${this._iconSVG.outerHTML} ${this._label}
`; + } else { + return ` +
+ ${this._iconSVG.outerHTML} ${this._label} + ${this._tooltip} +
+ `; + } } } diff --git a/src/ui/layout.ts b/src/ui/layout.ts index 6d053a4..2ebe505 100644 --- a/src/ui/layout.ts +++ b/src/ui/layout.ts @@ -378,7 +378,8 @@ export class UI { }) .isEnabled(() => { return Renderer.Get.getModelsAvailable() >= MeshType.TriangleMesh; - }), + }) + .setTooltip('View mesh'), 'voxelMesh': new ToolbarItemComponent({ id: 'voxelMesh', iconSVG: AppIcons.VOXEL }) .onClick(() => { Renderer.Get.setModelToUse(MeshType.VoxelMesh); @@ -388,7 +389,8 @@ export class UI { }) .isEnabled(() => { return Renderer.Get.getModelsAvailable() >= MeshType.VoxelMesh; - }), + }) + .setTooltip('View voxel mesh'), 'blockMesh': new ToolbarItemComponent({ id: 'blockMesh', iconSVG: AppIcons.BLOCK }) .onClick(() => { Renderer.Get.setModelToUse(MeshType.BlockMesh); @@ -398,7 +400,8 @@ export class UI { }) .isEnabled(() => { return Renderer.Get.getModelsAvailable() >= MeshType.BlockMesh; - }), + }) + .setTooltip('View block mesh'), }, componentOrder: ['mesh', 'voxelMesh', 'blockMesh'], }, @@ -413,14 +416,16 @@ export class UI { }) .isEnabled(() => { return Renderer.Get.getActiveMeshType() !== MeshType.None; - }), + }) + .setTooltip('Toggle grid'), 'axes': new ToolbarItemComponent({ id: 'axes', iconSVG: AppIcons.AXES }) .onClick(() => { Renderer.Get.toggleIsAxesEnabled(); }) .isActive(() => { return Renderer.Get.isAxesEnabled(); - }), + }) + .setTooltip('Toggle axes'), 'night-vision': new ToolbarItemComponent({ id: 'night', iconSVG: AppIcons.BULB }) .onClick(() => { Renderer.Get.toggleIsNightVisionEnabled(); @@ -430,7 +435,8 @@ export class UI { }) .isEnabled(() => { return Renderer.Get.canToggleNightVision(); - }), + }) + .setTooltip('Toggle night vision'), }, componentOrder: ['grid', 'axes', 'night-vision'], }, @@ -445,7 +451,8 @@ export class UI { }) .isActive(() => { return Renderer.Get.isSliceViewerEnabled(); - }), + }) + .setTooltip('Toggle slice viewer'), 'plus': new ToolbarItemComponent({ id: 'plus', iconSVG: AppIcons.PLUS }) .onClick(() => { Renderer.Get.incrementSliceHeight(); @@ -453,7 +460,8 @@ export class UI { .isEnabled(() => { return Renderer.Get.isSliceViewerEnabled() && Renderer.Get.canIncrementSliceHeight(); - }), + }) + .setTooltip('Decrement slice'), 'minus': new ToolbarItemComponent({ id: 'minus', iconSVG: AppIcons.MINUS }) .onClick(() => { Renderer.Get.decrementSliceHeight(); @@ -461,7 +469,8 @@ export class UI { .isEnabled(() => { return Renderer.Get.isSliceViewerEnabled() && Renderer.Get.canDecrementSliceHeight(); - }), + }) + .setTooltip('Increment slice'), }, componentOrder: ['slice', 'plus', 'minus'], }, @@ -476,15 +485,18 @@ export class UI { 'zoomOut': new ToolbarItemComponent({ id: 'zout', iconSVG: AppIcons.MINUS }) .onClick(() => { ArcballCamera.Get.onZoomOut(); - }), + }) + .setTooltip('Zoom out'), 'zoomIn': new ToolbarItemComponent({ id: 'zin', iconSVG: AppIcons.PLUS }) .onClick(() => { ArcballCamera.Get.onZoomIn(); - }), + }) + .setTooltip('Zoom in'), 'reset': new ToolbarItemComponent({ id: 'reset', iconSVG: AppIcons.CENTRE }) .onClick(() => { ArcballCamera.Get.reset(); - }), + }) + .setTooltip('Reset camera'), }, componentOrder: ['zoomOut', 'zoomIn', 'reset'], }, @@ -496,14 +508,16 @@ export class UI { }) .isActive(() => { return ArcballCamera.Get.isPerspective(); - }), + }) + .setTooltip('Perspective camera'), 'orthographic': new ToolbarItemComponent({ id: 'orth', iconSVG: AppIcons.ORTHOGRAPHIC }) .onClick(() => { ArcballCamera.Get.setCameraMode('orthographic'); }) .isActive(() => { return ArcballCamera.Get.isOrthographic(); - }), + }) + .setTooltip('Orthographic camera'), }, componentOrder: ['perspective', 'orthographic'], }, diff --git a/src/util/ui_util.ts b/src/util/ui_util.ts index 06e8d32..35bf8db 100644 --- a/src/util/ui_util.ts +++ b/src/util/ui_util.ts @@ -14,31 +14,24 @@ export namespace UIUtil { } export function clearStyles(element: HTMLElement) { - element.classList.remove('style-inactive-disabled'); - element.classList.remove('style-inactive-enabled'); - element.classList.remove('style-inactive-hover'); - element.classList.remove('style-active-disabled'); - element.classList.remove('style-active-enabled'); - element.classList.remove('style-active-hover'); + element.classList.remove('disabled'); + element.classList.remove('hover'); + element.classList.remove('active'); } export function updateStyles(element: HTMLElement, style: TStyleParams) { clearStyles(element); - let styleToApply = `style`; - - styleToApply += style.isActive ? '-active' : '-inactive'; - - if (style.isEnabled) { - if (style.isHovered) { - styleToApply += '-hover'; - } else { - styleToApply += '-enabled'; - } - } else { - styleToApply += '-disabled'; + if (style.isActive) { + element.classList.add('active'); } - element.classList.add(styleToApply); + if (!style.isEnabled) { + element.classList.add('disabled'); + } + + if (style.isHovered && style.isEnabled) { + element.classList.add('hover'); + } } } diff --git a/styles.css b/styles.css index b00fb7f..58c34e8 100644 --- a/styles.css +++ b/styles.css @@ -253,6 +253,9 @@ select { transition: width 0.2s; } + + + .struct-prop { display: flex; align-items: center; @@ -264,42 +267,49 @@ select { border-style: solid; } -.style-inactive-disabled { +.struct-prop.disabled { border-color: var(--gray-500); color: var(--text-dark); background: var(--gray-400); cursor: inherit; } -.style-inactive-enabled { - border-color: var(--gray-600); - color: var(--text-standard); - background: var(--gray-500); -} -.style-inactive-hover { + +.struct-prop.hover { border-color: var(--gray-700); color: var(--text-light); background: var(--gray-600); cursor: pointer; } -.style-active-disabled { +.struct-prop:not(.disabled):not(.hover) { + border-color: var(--gray-600); + color: var(--text-standard); + background: var(--gray-500); +} + +.struct-prop.active.disabled { border-color: var(--blue-450); color: var(--text-dim); background: var(--blue-400); cursor: inherit; } -.style-active-enabled { - border-color: var(--blue-600); - color: var(--text-bright); - background: var(--blue-500); -} -.style-active-hover { + +.struct-prop.active.hover { border-color: var(--blue-700); color: var(--text-bright); background: var(--blue-600); cursor: pointer; } +.struct-prop.active:not(.disabled):not(.hover) { + border-color: var(--blue-600); + color: var(--text-bright); + background: var(--blue-500); +} + + + + .h-div { height: 0px; border-radius: 2px; @@ -348,7 +358,7 @@ select { justify-content: center; flex-grow: 1; } -.spinbox-value.style-inactive-hover { +.spinbox-value .inactive .hover { cursor: e-resize; } @@ -676,4 +686,52 @@ a { .hide { display: none; +} + + + +.tooltip-text { + visibility: hidden; + opacity: 0; + position: absolute; + z-index: 1; + font-size: var(--font-size-small); + color: var(--text-light); + background-color: var(--gray-600); + padding: 5px 10px; + border-radius: 5px; + border: 1px solid var(--gray-700); + transition: 0.15s; + pointer-events: none; + box-shadow: rgba(0, 0, 0, 0.1) 0px 0px 16px; + white-space: nowrap; +} + +.hover-text:hover:not(.disabled) .tooltip-text { + visibility: visible; + opacity: 1; +} + +.top { + top: -40px; + left: -50%; +} + +.bottom { + top: 25px; + left: -50%; +} + +.left { + top: 1px; + right: 120%; +} + +.right { + top: 2px; + left: 120%; +} + +.hover-text { + position: relative; } \ No newline at end of file