Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
faa6686
feat: scaffolding for stacking switch
seankmartin Jan 3, 2025
3125140
CC-166 make layout be able to change in both sides
Aiga115 Jan 6, 2025
5f88487
CC-166 revert style for dropzone
Aiga115 Jan 6, 2025
f0503e1
CC-166 add check
Aiga115 Jan 6, 2025
5e01c4f
CC-166 delete unnecessary code
Aiga115 Jan 6, 2025
9b4f752
refactor: update checkbox disable svg logic
seankmartin Jan 8, 2025
e9b8a7e
fix: handle stacking change
seankmartin Jan 8, 2025
c89d9a9
CC-166 small ui fix
Aiga115 Jan 9, 2025
81b11f9
chore: formatting pass
seankmartin Jan 9, 2025
b9a5938
chore: linting
seankmartin Jan 9, 2025
8813696
feat: restore vertical scroll bar in horizontal stacking mode
seankmartin Jan 9, 2025
b007f9a
Merge branch 'master' into feature/CC-166
seankmartin Jan 20, 2025
b51a4ab
refactor: use attribute in CSS as opposed to class for horizontal/ver…
seankmartin Jan 23, 2025
9e6e020
feat: clarify stacking is of groups not tools
seankmartin Jan 23, 2025
33347a7
CC-212 make changes to tool palette horizontal stacking ui
Aiga115 Feb 2, 2025
6c74a0b
CC-212 make layer goup items to be aligned at the top
Aiga115 Feb 4, 2025
55a66cd
Merge pull request #69 from MetaCell/feature/CC-212
seankmartin Feb 6, 2025
c02a542
Merge branch 'master' into feature/CC-166
seankmartin Feb 6, 2025
918c20b
fix: remove temp throttle experiment
seankmartin Feb 6, 2025
cee5740
Merge branch 'master' into feature/CC-166
seankmartin Feb 12, 2025
9f3a789
Merge branch 'master' into feature/CC-166
seankmartin Mar 4, 2025
c55d9a7
fix: allow drop tools into empty horizontal palette
seankmartin Mar 17, 2025
dd561ae
fix: correct query palette drop message showing
seankmartin Mar 18, 2025
9833b42
feat: disable dropping tools into horizontal stacking
seankmartin Mar 18, 2025
e06626c
feat: more informative non-horizontal stacking message
seankmartin Mar 18, 2025
4d8508f
fix: clear horizontal stacking message
seankmartin Mar 18, 2025
e3757ee
fix: remove horizontal drop zone with query
seankmartin Mar 18, 2025
63d78cc
fix: allow moving tools in horizontal mode again
seankmartin Mar 18, 2025
4d063b1
Merge branch 'master' into feature/improve-stacking
seankmartin Mar 18, 2025
9860d69
feat: auto change stacking direction in palette
seankmartin Mar 18, 2025
7852741
feat: auto mode is enabled by default, can be manually disabled
seankmartin Mar 18, 2025
2f05f85
Merge branch 'master' into feature/CC-166
seankmartin Mar 18, 2025
ab1d8cc
Merge branch 'feature/CC-166' into feature/improve-stacking
seankmartin Mar 18, 2025
168e7b4
fix: stop vertical scroll bar showing when not needed in horizontal mode
seankmartin Mar 25, 2025
bec9186
feature/improve-stacking fix text being off screen
Aiga115 Mar 25, 2025
6b25bc9
Merge pull request #76 from MetaCell/feature/improve-stacking
seankmartin Mar 31, 2025
31a050c
CC-166 Move stackingMode state management to the CheckboxIcon
aranega Apr 18, 2025
de9da4f
CC-166 Fix format
aranega Apr 18, 2025
4de4a2c
Merge branch 'master' into feature/CC-166
seankmartin Apr 29, 2025
bc6af82
fix: correct state setting
seankmartin Apr 29, 2025
e8a4a77
fix: restore to full width
seankmartin May 12, 2025
cabcc8a
Merge branch 'master' into feature/CC-166
seankmartin May 19, 2025
a749509
Merge branch 'master' into feature/CC-166
seankmartin May 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions src/ui/tool_palette.css
Original file line number Diff line number Diff line change
Expand Up @@ -99,17 +99,51 @@
flex-grow: 1;
flex-shrink: 1;
flex-basis: 0px;
font: 10pt sans-serif;
overflow-y: auto;
overflow-x: hidden;
}

.neuroglancer-tool-palette-layer-group-items {
font: 10pt sans-serif;
display: flex;
}

.neuroglancer-tool-palette-layer-group-items[tool-stacking="vertical"] {
flex-direction: column;
}

.neuroglancer-tool-palette-layer-group-items[tool-stacking="horizontal"] {
flex-direction: row;
align-items: flex-start;
overflow-x: auto;
}

.neuroglancer-tool-palette-layer-group-items[tool-stacking="horizontal"][has-tools="true"] {
height: 100%;
}

.neuroglancer-tool-palette-layer-group-items[tool-stacking="horizontal"][has-tools="false"] {
height: 0;
}

.neuroglancer-tool-palette-layer-group {
width: 100%;
height: 100%;
box-sizing: border-box;
}

.neuroglancer-tool-palette-layer-group-content
> .neuroglancer-tool-palette-tool-container {
align-items: center;
}

.neuroglancer-tool-palette-tool-container {
display: grid;
grid-template-columns: auto 1fr;
align-items: center;
align-items: flex-start;
padding-top: 2px;
padding-bottom: 2px;
width: 100%;
}

.neuroglancer-tool-palette-tool-container:hover {
Expand Down
141 changes: 129 additions & 12 deletions src/ui/tool_palette.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@
import "#src/ui/tool_palette.css";

import svg_search from "ikonate/icons/search.svg?raw";
import swap_horizontal from "ikonate/icons/swap-horizontal.svg?raw";
import swap_vertical from "ikonate/icons/swap-vertical.svg?raw";
import svg_tool from "ikonate/icons/tool.svg?raw";
import { debounce } from "lodash-es";
import type { UserLayer } from "#src/layer/index.js";
import {
ElementVisibilityFromTrackableBoolean,
TrackableBoolean,
TrackableBooleanCheckbox,
} from "#src/trackable_boolean.js";
import {
Expand Down Expand Up @@ -84,6 +87,7 @@ import {
import { NullarySignal } from "#src/util/signal.js";
import type { Trackable } from "#src/util/trackable.js";
import { CompoundTrackable, getCachedJson } from "#src/util/trackable.js";
import { TrackableEnum } from "#src/util/trackable_enum.js";
import type { Viewer } from "#src/viewer.js";
import { CheckboxIcon } from "#src/widget/checkbox_icon.js";
import { makeDeleteButton } from "#src/widget/delete_button.js";
Expand All @@ -98,6 +102,14 @@ import {
} from "#src/widget/multiline_autocomplete.js";
import { TextInputWidget } from "#src/widget/text_input.js";

enum StackingMode {
AUTO = 0,
VERTICAL = 1,
HORIZONTAL = 2,
}

type TrackableStackingMode = TrackableEnum<StackingMode>;

const DEFAULT_TOOL_PALETTE_PANEL_LOCATION: SidePanelLocation = {
...DEFAULT_SIDE_PANEL_LOCATION,
side: "right",
Expand Down Expand Up @@ -272,6 +284,11 @@ export class ToolPaletteState extends RefCounted implements Trackable {
get changed() {
return this.trackable.changed;
}
stackingMode: TrackableStackingMode = new TrackableEnum(
StackingMode,
StackingMode.AUTO,
);
verticalStacking = new TrackableBoolean(true, true);

constructor(public viewer: Viewer) {
super();
Expand All @@ -280,6 +297,7 @@ export class ToolPaletteState extends RefCounted implements Trackable {
this.name.changed.add(this.changed.dispatch);
this.trackable.add("tools", this.tools);
this.trackable.add("query", this.query);
this.trackable.add("stacking", this.stackingMode);
this.queryDefined = this.registerDisposer(
makeCachedDerivedWatchableValue(
(value) => value.length === 0,
Expand All @@ -294,6 +312,12 @@ export class ToolPaletteState extends RefCounted implements Trackable {
if (this.query.value !== "") {
this.tools.reset();
}
if (this.stackingMode.value === StackingMode.VERTICAL) {
this.verticalStacking.value = true;
}
if (this.stackingMode.value === StackingMode.HORIZONTAL) {
this.verticalStacking.value = false;
}
}

reset() {
Expand Down Expand Up @@ -512,13 +536,16 @@ class RenderedTool extends RefCounted {

export class ToolPalettePanel extends SidePanel {
private itemContainer = document.createElement("div");
private layerGroupItemsContainer = document.createElement("div");
private dropZone = document.createElement("div");
private renderedTools = new Map<Tool, RenderedTool>();
private dragState:
| { dragSource: ToolDragSource; ephemeralTool: Tool }
| undefined = undefined;
private dragEnterCount = 0;
private mousePositionOnLastDrag: [number, number] | undefined = undefined;
private queryResults: QueryResults;
private resizeGeneration = -1;

private clearDragState() {
const { dragState } = this;
Expand Down Expand Up @@ -549,11 +576,7 @@ export class ToolPalettePanel extends SidePanel {
if (this.hasQuery) {
this.clearDragState();
const otherPalette = toolDragSource?.paletteState?.palette;
if (
updateDropEffect &&
otherPalette !== undefined &&
otherPalette !== this
) {
if (updateDropEffect && otherPalette !== this) {
pushDragStatus(
event,
this.itemContainer,
Expand Down Expand Up @@ -632,6 +655,7 @@ export class ToolPalettePanel extends SidePanel {
};

const handleDragOver = (event: DragEvent) => {
this.mousePositionOnLastDrag = [event.clientX, event.clientY];
const updateResult = update(event, /*updateDropEffect=*/ true);
if (updateResult === undefined) {
popDragStatus(event, this.itemContainer, "drop");
Expand All @@ -648,7 +672,20 @@ export class ToolPalettePanel extends SidePanel {
element.addEventListener("dragleave", (event: DragEvent) => {
if (--this.dragEnterCount !== 0) return;
popDragStatus(event, this.itemContainer, "drop");
this.clearDragState();
if (this.state.verticalStacking.value) {
this.clearDragState();
}
// In horizontal mode, the tools can move themselves
// So we need to check if the mouse cursor actually moved
else {
const [x, y] = this.mousePositionOnLastDrag!;
if (
Math.abs(x - event.clientX) > 1 ||
Math.abs(y - event.clientY) > 1
) {
this.clearDragState();
}
}
event.stopPropagation();
});
element.addEventListener("drop", (event: DragEvent) => {
Expand Down Expand Up @@ -686,7 +723,35 @@ export class ToolPalettePanel extends SidePanel {
const hasQuery = this.registerDisposer(
makeCachedDerivedWatchableValue((value) => value !== "", [state.query]),
);
this.registerDisposer(
this.sidePanelManager.display.changed.add(() =>
this.autoDetermineStacking(),
),
);
const self = this;
const changeStackingButton = this.registerDisposer(
new CheckboxIcon(
{
changed: self.state.verticalStacking.changed,
get value() {
return self.state.verticalStacking.value;
},
set value(newValue: boolean) {
self.state.verticalStacking.value = newValue;
self.state.stackingMode.value = newValue
? StackingMode.VERTICAL
: StackingMode.HORIZONTAL;
},
},
{
svg: swap_horizontal,
disableSvg: swap_vertical,
enableTitle: "Swap to vertical group stacking",
disableTitle: "Swap to horizontal group stacking",
},
),
);
titleBar.appendChild(changeStackingButton.element);
const searchButton = this.registerDisposer(
new CheckboxIcon(
{
Expand Down Expand Up @@ -718,8 +783,11 @@ export class ToolPalettePanel extends SidePanel {

const body = document.createElement("div");
body.classList.add("neuroglancer-tool-palette-body");
const { itemContainer } = this;
const { itemContainer, layerGroupItemsContainer } = this;
itemContainer.classList.add("neuroglancer-tool-palette-items");
layerGroupItemsContainer.classList.add(
"neuroglancer-tool-palette-layer-group-items",
);
body.appendChild(
this.registerDisposer(
new DependentViewWidget(
Expand All @@ -733,21 +801,72 @@ export class ToolPalettePanel extends SidePanel {
),
).element,
);
itemContainer.appendChild(layerGroupItemsContainer);
body.appendChild(itemContainer);
this.addBody(body);

const { dropZone } = this;
dropZone.classList.add("neuroglancer-tool-palette-drop-zone");
itemContainer.appendChild(dropZone);
this.registerDropHandlers(dropZone, () => undefined);
const debouncedRender = this.registerCancellable(
animationFrameDebounce(() => this.render()),
);
this.registerDisposer(this.state.tools.changed.add(debouncedRender));
this.registerDisposer(this.queryResults.changed.add(debouncedRender));
this.registerDisposer(
this.state.tools.changed.add(() => {
this.handleNumToolsChange();
debouncedRender();
}),
);
this.registerDisposer(
this.queryResults.changed.add(() => {
this.handleNumToolsChange();
debouncedRender();
}),
);
this.registerDisposer(
this.state.verticalStacking.changed.add(() => {
this.handleStackingChange();
debouncedRender();
}),
);
this.visibility.changed.add(debouncedRender);
this.autoDetermineStacking();
this.handleStackingChange();
this.handleNumToolsChange();
this.render();
}

private autoDetermineStacking() {
if (this.state.stackingMode.value !== StackingMode.AUTO) return;
if (
this.resizeGeneration === this.sidePanelManager.display.resizeGeneration
)
return;
const { width, height } = this.element.getBoundingClientRect();
if (height === 0 || width === 0) return;
this.resizeGeneration = this.sidePanelManager.display.resizeGeneration;
this.state.verticalStacking.value = height > 0.4 * width;
}

private handleNumToolsChange() {
// If in horizontal stacking mode, the palette gets set to full height when there
// is at least one tool
this.layerGroupItemsContainer.setAttribute(
"has-tools",
this.state.tools.tools.length > 0 || this.queryResults.value.length > 0
? "true"
: "false",
);
}

private handleStackingChange() {
this.layerGroupItemsContainer.setAttribute(
"tool-stacking",
this.state.verticalStacking.value ? "vertical" : "horizontal",
);
}

private getRenderedTool(tool: Tool) {
const { renderedTools } = this;
let renderedTool = renderedTools.get(tool);
Expand Down Expand Up @@ -837,10 +956,8 @@ export class ToolPalettePanel extends SidePanel {
renderedTools.delete(tool);
}
}

yield self.dropZone;
}
updateChildren(this.itemContainer, getItems());
updateChildren(this.layerGroupItemsContainer, getItems());
}

disposed() {}
Expand Down
9 changes: 8 additions & 1 deletion src/widget/checkbox_icon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@ import { RefCounted } from "#src/util/disposable.js";
import type { MakeIconOptions } from "#src/widget/icon.js";
import { makeIcon } from "#src/widget/icon.js";

// disableSvg is a new option that allows to replace the svg with a custom svg when the checkbox is disabled
// this replaces the default behavior of changing the color of the svg
export interface MakeCheckboxIconOptions
extends Omit<MakeIconOptions, "onClick" | "title"> {
enableTitle?: string;
disableTitle?: string;
backgroundScheme?: "light" | "dark";
disableSvg?: string;
}

export class CheckboxIcon extends RefCounted {
Expand All @@ -49,9 +52,13 @@ export class CheckboxIcon extends RefCounted {
);
const updateView = () => {
const value = model.value;
this.element.dataset.checked = value ? "true" : "false";
this.element.title =
(value ? options.disableTitle : options.enableTitle) || "";
if (options.disableSvg && options.svg) {
this.element.innerHTML = value ? options.disableSvg : options.svg;
} else {
this.element.dataset.checked = value ? "true" : "false";
}
};
this.registerDisposer(model.changed.add(updateView));
updateView();
Expand Down
Loading