Skip to content

Commit

Permalink
Dropdown improvements (#10853)
Browse files Browse the repository at this point in the history
Implement changes requested in #10666.

#### Align left edge with widget

Top-level argument (edge aligned to TLA boundary):

<img width="639" alt="Screenshot 2024-08-20 at 10 10 27" src="https://github.com/user-attachments/assets/eb1712bf-1cbc-4a5d-bdb6-7458f94a27f4">

Nested (edge aligned to start of widget text):

<img width="519" alt="Screenshot 2024-08-20 at 10 11 47" src="https://github.com/user-attachments/assets/477ffe6e-ac00-41fb-bfb0-4d22c25c0f8b">

#### Adjust height

<img width="134" alt="Screenshot 2024-08-20 at 10 06 20" src="https://github.com/user-attachments/assets/17ac485e-deeb-4afd-973b-fc8c182ffb1d">

# Important Notes
- Also some HTML/CSS simplification in `CircularMenu`.
  • Loading branch information
kazcw authored Aug 21, 2024
1 parent 2919e58 commit 45a54b9
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 53 deletions.
2 changes: 1 addition & 1 deletion app/gui2/e2e/locate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export const graphEditor = componentLocator('.GraphEditor')
export const codeEditor = componentLocator('.CodeEditor')
export const anyVisualization = componentLocator('.GraphVisualization > *')
export const loadingVisualization = componentLocator('.LoadingVisualization')
export const circularMenu = componentLocator('.CircularMenu > .circle')
export const circularMenu = componentLocator('.CircularMenu')
export const addNewNodeButton = componentLocator('.PlusButton')
export const componentBrowser = componentLocator('.ComponentBrowser')
export const nodeOutputPort = componentLocator('.outputPortHoverArea')
Expand Down
54 changes: 22 additions & 32 deletions app/gui2/src/components/CircularMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,14 @@ function readableBinding(binding: keyof (typeof graphBindings)['bindings']) {
</script>

<template>
<div class="CircularMenu">
<div
v-if="!showColorPicker"
class="circle menu"
:class="`${props.isFullMenuVisible ? 'full' : 'partial'}`"
>
<div
class="CircularMenu"
:class="{
partial: !props.isFullMenuVisible,
menu: !showColorPicker,
}"
>
<template v-if="!showColorPicker">
<template v-if="isFullMenuVisible">
<SvgButton
v-if="documentationUrl"
Expand Down Expand Up @@ -94,24 +96,24 @@ function readableBinding(binding: keyof (typeof graphBindings)['bindings']) {
:modelValue="props.isRecordingOverridden"
@update:modelValue="emit('update:isRecordingOverridden', $event)"
/>
</div>
<div v-if="showColorPicker" class="circle">
<ColorRing
v-model="nodeColor"
:matchableColors="matchableNodeColors"
:initialColorAngle="90"
@close="showColorPicker = false"
/>
</div>
</template>
<ColorRing
v-else
v-model="nodeColor"
:matchableColors="matchableNodeColors"
:initialColorAngle="90"
@close="showColorPicker = false"
/>
</div>
</template>

<style scoped>
.CircularMenu {
position: absolute;
bottom: 0;
width: 0;
height: 0;
left: -36px;
top: -36px;
width: var(--outer-diameter);
height: var(--outer-diameter);
user-select: none;
pointer-events: none;
/* This is a variable so that it can be referenced in computations,
Expand All @@ -123,15 +125,7 @@ function readableBinding(binding: keyof (typeof graphBindings)['bindings']) {
);
}
.circle {
position: relative;
left: -36px;
top: -68px;
width: var(--outer-diameter);
height: var(--outer-diameter);
}
.circle.menu {
.menu {
> * {
pointer-events: all;
}
Expand All @@ -144,6 +138,7 @@ function readableBinding(binding: keyof (typeof graphBindings)['bindings']) {
width: 100%;
height: 100%;
pointer-events: all;
clip-path: var(--full-ring-path);
}
&.partial {
Expand All @@ -154,11 +149,6 @@ function readableBinding(binding: keyof (typeof graphBindings)['bindings']) {
);
}
}
&.full {
&:before {
clip-path: var(--full-ring-path);
}
}
}
.More {
Expand Down
4 changes: 0 additions & 4 deletions app/gui2/src/components/GraphEditor/GraphNode.vue
Original file line number Diff line number Diff line change
Expand Up @@ -627,10 +627,6 @@ watchEffect(() => {
z-index: 25;
}
.CircularMenu.partial {
z-index: 1;
}
.beforeNode {
position: absolute;
bottom: 100%;
Expand Down
35 changes: 24 additions & 11 deletions app/gui2/src/components/GraphEditor/widgets/WidgetSelection.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script setup lang="ts">
import NodeWidget from '@/components/GraphEditor/NodeWidget.vue'
import { enclosingTopLevelArgument } from '@/components/GraphEditor/widgets/WidgetTopLevelArgument.vue'
import SizeTransition from '@/components/SizeTransition.vue'
import SvgIcon from '@/components/SvgIcon.vue'
import DropdownWidget, { type DropdownEntry } from '@/components/widgets/DropdownWidget.vue'
Expand Down Expand Up @@ -36,7 +37,7 @@ const graph = useGraphStore()
const tree = injectWidgetTree()
const widgetRoot = ref<HTMLElement>()
const widgetRoot = shallowRef<HTMLElement>()
const dropdownElement = ref<HTMLElement>()
const activityElement = ref<HTMLElement>()
Expand All @@ -50,8 +51,13 @@ const activity = shallowRef<VNode>()
// Any text beyond that limit will receive an ellipsis and sliding animation on hover.
const MAX_DROPDOWN_OVERSIZE_PX = 150
const floatReference = computed(
() => enclosingTopLevelArgument(widgetRoot.value, tree) ?? widgetRoot.value,
)
function dropdownStyles(dropdownElement: Ref<HTMLElement | undefined>, limitWidth: boolean) {
return useFloating(widgetRoot, dropdownElement, {
return useFloating(floatReference, dropdownElement, {
placement: 'bottom-start',
middleware: computed(() => {
return [
offset((state) => {
Expand Down Expand Up @@ -471,18 +477,21 @@ declare module '@/providers/widgetRegistry' {
<SvgIcon v-else name="arrow_right_head_only" class="arrow widgetOutOfLayout" />
</template>
<Teleport v-if="tree.nodeElement" :to="tree.nodeElement">
<div ref="dropdownElement" :style="floatingStyles" class="widgetOutOfLayout">
<div ref="dropdownElement" :style="floatingStyles" class="widgetOutOfLayout floatingElement">
<SizeTransition height :duration="100">
<div v-if="dropDownInteraction.isActive() && activity == null">
<DropdownWidget
color="var(--node-color-primary)"
:entries="entries"
@clickEntry="onClick"
/>
</div>
<DropdownWidget
v-if="dropDownInteraction.isActive() && activity == null"
color="var(--node-color-primary)"
:entries="entries"
@clickEntry="onClick"
/>
</SizeTransition>
</div>
<div ref="activityElement" class="activityElement" :style="activityStyles">
<div
ref="activityElement"
class="activityElement widgetOutOfLayout floatingElement"
:style="activityStyles"
>
<SizeTransition height :duration="100">
<div v-if="dropDownInteraction.isActive() && activity">
<component :is="activity" />
Expand All @@ -502,6 +511,10 @@ declare module '@/providers/widgetRegistry' {
min-height: var(--node-port-height);
}
.floatingElement {
z-index: 21;
}
svg.arrow {
position: absolute;
bottom: -8px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,27 @@ export const widgetDefinition = defineWidget(
},
import.meta.hot,
)
/** If the element is the recursively-first-child of a top-level argument, return the top-level argument element. */
export function enclosingTopLevelArgument(
element: HTMLElement | undefined,
tree: { nodeElement: HTMLElement | undefined },
): HTMLElement | undefined {
return (
element?.dataset.topLevelArgument !== undefined ? element
: (
!element ||
element === tree.nodeElement ||
element.parentElement?.firstElementChild !== element
) ?
undefined
: enclosingTopLevelArgument(element.parentElement, tree)
)
}
</script>

<template>
<div class="WidgetTopLevelArgument widgetResetPadding">
<div class="WidgetTopLevelArgument widgetResetPadding" data-top-level-argument>
<NodeWidget :input="props.input" />
</div>
</template>
Expand Down
9 changes: 5 additions & 4 deletions app/gui2/src/components/widgets/DropdownWidget.vue
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,16 @@ export interface DropdownEntry {
<style scoped>
.DropdownWidget {
--item-height: 23px;
--visible-items: 6.4;
--dropdown-padding: 6px;
--item-padding: 8px;
/* When dropdown is displayed right below the last node's argument, the rounded corner needs to be
covered. This is done by covering extra node-sized space at the top of the dropdown. */
--dropdown-extend: calc(var(--node-base-height) - var(--extend-margin));
position: relative;
user-select: none;
min-width: 100%;
z-index: 21;
margin-top: calc(0px - var(--dropdown-extend));
padding-top: var(--dropdown-extend);
background-color: var(--dropdown-bg);
Expand All @@ -127,7 +128,7 @@ export interface DropdownEntry {
overflow: auto;
min-width: 100%;
min-height: 16px;
max-height: 152px;
max-height: calc(var(--visible-items) * var(--item-height) + 2 * var(--dropdown-padding));
list-style-type: none;
color: var(--color-text-light);
scrollbar-width: thin;
Expand All @@ -136,8 +137,8 @@ export interface DropdownEntry {
}
.item {
padding-left: 8px;
padding-right: 8px;
padding-left: var(--item-padding);
padding-right: var(--item-padding);
border-radius: calc(var(--item-height) / 2);
height: var(--item-height);
text-align: left;
Expand Down

0 comments on commit 45a54b9

Please sign in to comment.