Drag & Drop: как это реально устроено в TakeProfit — и как сделал бы Apple
По запросу — копнул глубже. Ниже: полная инвентаризация всех DnD-взаимодействий, фактика прямо из их бандлов (пороги, зоны, golden-ratio, магнит), 4 интерактивных демо «потрогай руками», и как Apple переосмыслил бы перетаскивание для mobile+desktop.
Инвентарь: где в TakeProfit есть drag & drop
DnD у них не один — это десяток разных механик. Раньше я свёл всё к «drag-to-dock», что было поверхностно. Полный список:
| # | Где | Что делает drag | Движок |
|---|---|---|---|
| 1 | Докинг виджетов | Тащишь панель → док к краю/центру/во вкладку; сплит по золотому сечению | Lumino DockPanel |
| 2 | Вкладки виджетов | Reorder вкладок; «отрыв» вкладки в плавающее окно | Lumino TabBar |
| 3 | Разделители панелей | Тащишь сплиттер → ресайз соседних пейнов | Lumino SplitPanel |
| 4 | Объекты на графике (целиком) | Тащишь весь рисунок (трендлайн, прямоугольник…) → перенос | Chart engine |
| 5 | Контрольные точки (anchors) | Тащишь отдельную точку → меняешь геометрию объекта | Chart engine · hitTest |
| 6 | Серединные точки (midpoints) | Тащишь середину сегмента → сдвиг/добавление точки | Chart engine |
| 7 | Индикаторы между пейнами | Перетаскиваешь индикатор в другой пейн / в новый | Chart + reorder |
| 8 | Reorder watchlist / легенды | Меняешь порядок строк с placeholder-гэпом | Custom reorder |
| 9 | Символ → график | Перетаскиваешь тикер из watchlist/скринера на чарт → загрузка | MimeData transfer |
| 10 | Линии ордеров/уровней | Тащишь стоп/лимит/алерт-линию вверх-вниз по цене | Chart engine |
| 11 | Пан/зум графика | Drag = панорамирование времени; края осей = сжатие/растяжение | Chart engine |
| 12 | Drop файла/картинки | Бросаешь изображение/ассет в нужный виджет | transfer_float |
Как это устроено в коде (фактика из бандлов)
Не «по ощущениям», а из их минифицированных бандлов BgH94ARf.js (движок) и C3wOGcEP.js (Lumino-докинг).
Докинг виджетов (Lumino DockPanel)
| Константа | Значение | Смысл |
|---|---|---|
| DRAG_THRESHOLD | 5px | Сдвиг указателя, после которого начинается drag (отсечь случайные клики) |
| DETACH_THRESHOLD | 20px | На сколько утащить вкладку за пределы таб-бара, чтобы она «оторвалась» в плавающее окно |
| GOLDEN_RATIO | .618 | Пропорция сплита при доке к краю |
| DEFAULT_EDGES | {top:12, right:40, bottom:40, left:40} | Толщина краевых drop-зон (верх тоньше — там тулбары) |
Зоны попадания (findDropTarget → zone)
zone: "root-top" | "root-right" | "root-bottom" | "root-left" | "root-all"
| "widget-all" // дроп по центру виджета = вложить во вкладку
| "widget-tab" // дроп на таб-бар = вставить вкладку в позицию
| "invalid"
op: addWidget · moveWidget · insertWidget · insertTab · SplitNode
feedback: lm-DockPanel-overlay (синий прямоугольник) · lm-mod-drag-image (ghost) · overrideCursor
Передача данных — через Lumino MimeData (setData/getData), результат — proposedAction → dropAction из набора copy · move · link · none (если none — дроп отклоняется, overlay прячется).
Объекты на графике
pointerdown → hitTest(x,y) → что под курсором: AnchorPoint · midpoint · whole object · resize-handle
→ startDrag → dragMove → dragEnd
// привязки во время drag:
magnet: magnetThreshold: 16 // px — притяжение точки к OHLC при удержании ⌘
angle: snapToAngle // Shift → 45°/90°
bar: _getXThreshold // привязка X к ближайшей свече
Reorder списков
Watchlist/легенда — кастомный drag-reorder с placeholder-гэпом (строка-дырка на месте перетаскиваемого элемента), без сторонней sortable-библиотеки.
Интерактивные демо — потрогай
4 живых демо прямо здесь (мышь или палец). Это упрощённые модели реальных механик TakeProfit.
Тащи плашку «Watchlist» в стейдж. Подсветятся зоны (как у Lumino: лево/право/верх/низ = сплит .618, центр = вкладка). Отпусти — панель докнётся.
Тащи строку вверх/вниз — остальные расступаются (placeholder), как в watchlist.
- ⋮⋮BTC/USD+0.18%
- ⋮⋮ETH/USD+0.09%
- ⋮⋮SOL/USD−1.2%
- ⋮⋮DOGE/USD+0.8%
- ⋮⋮TSLA−0.4%
Тащи синюю точку. При включённом магните она прилипает к close ближайшей свечи в радиусе 16px (как magnetThreshold:16).
Тащи тикер на область графика — он «загрузится» (так dnd из watchlist/скринера меняет символ).
Где DnD TakeProfit ломается на тач
Проблема
Магнит = удержание ⌘. На телефоне нет ⌘ → точную привязку не вызвать.
Как должно быть
Магнит — это режим/тоггл в плашке + авто-снап по умолчанию на тач; ⌘ — лишь ускоритель на десктопе.
Проблема
Anchor-точки крошечные. Хит-таргет точки рисунка << 44pt — пальцем не попасть.
Как должно быть
На тач — увеличенная невидимая хит-зона ≥44pt вокруг точки + «лупа»/смещение для точного позиционирования под пальцем.
Проблема
Docking/детач — мышиная парадигма (drag header, drop overlay). На телефоне неуместна.
Как должно быть
В compact — нет докинга: свайп между полноэкранными виджетами; DnD-перекомпоновка только в regular/expanded.
Проблема
Drag конфликтует со скроллом/паном. На тач непонятно: я тащу объект или панорамирую график?
Как должно быть
Long-press, чтобы «поднять» объект (lift), затем drag; короткий drag по пустому = пан. Чёткое разделение жестов.
Как Apple сделал бы Drag & Drop
У Apple DnD — это формализованная система (UIDragInteraction/UIDropInteraction + NSItemProvider), единая на iPhone/iPad/Mac, с продуманными жестами, анимациями и доступностью.
Модель «поднять → нести → положить» (lift · drag · set-down)
| Фаза | Touch (iOS/iPadOS) | Pointer (Mac) |
|---|---|---|
| Начало | Long-press = lift (объект «приподнимается» с тенью + лёгкий хаптик) | press + сдвиг > порога (как DRAG_THRESHOLD 5px) |
| Несёшь | Палец тащит «preview»; можно второй рукой листать/менять экран | курсор + ghost; меняется на drop-курсор |
| Цель | Подсветка drop-зоны + drop proposal (copy/move/forbidden бейдж) | То же + overlay-индикатор (как Lumino) |
| Конец | Spring set-down анимация на место; хаптик подтверждения | плавное «защёлкивание» |
Что Apple добавил бы поверх текущего TakeProfit
Spring-loading
Задержал перетаскиваемое над свёрнутой группой/вкладкой — она авто-раскрывается, чтобы можно было дропнуть внутрь. Незаменимо для глубоких раскладок.
Item Providers
Тащимый объект несёт типизированные данные (символ, индикатор-конфиг, рисунок) → можно дропнуть и внутри приложения, и в другое приложение (в Notes, Mail).
Continuity-drag
Через Universal Control / Sidecar — тащишь рисунок или watchlist с iPad на Mac одним жестом. Одна модель данных это позволяет.
Apple Pencil
На iPad — Pencil для точного драга anchor-точек и рисования; палец остаётся для пана/скролла (разделение ввода).
Хаптика + звук
Lift, валидная цель, set-down — каждый этап с тактильным/звуковым подтверждением. Drag перестаёт быть «вслепую».
Доступность DnD
VoiceOver-режим перетаскивания: «выбрать как источник» → «выбрать как цель» без самого жеста. Drag доступен незрячим.
Принципы DnD (свод)
- Чёткий порог старта (≈5px / long-press) — не путать клик и drag
- Lift: объект визуально «поднимается» (тень/масштаб/хаптик) — видно, что схвачено
- Live-feedback цели: подсветка зоны + drop-proposal (copy/move/forbidden)
- Хит-зоны ≥44pt на тач (особенно anchor-точки); «лупа» для точности
- Магнит/снап — режим по умолчанию на тач, ускоритель (⌘/Shift) на десктопе
- Разделить жесты: long-press = поднять объект, drag по пустому = пан
- Spring-loading: задержка над группой раскрывает её
- Типизированные данные (item provider) → дроп внутри и между приложениями
- Set-down: пружинная анимация на место + подтверждение
- Docking/детач — только expanded; в compact свайп + switcher
- Continuity: синхрон раскладок, drag между устройствами
- Доступность: VoiceOver drag-режим без жеста
Источники
Фактика по TakeProfit — грэп их бандлов: C3wOGcEP.js (Lumino DockPanel: DRAG_THRESHOLD=5, DETACH_THRESHOLD=20, GOLDEN_RATIO=.618, DEFAULT_EDGES, зоны, mimeData/dropAction) и BgH94ARf.js (движок: hitTest/startDrag, AnchorPoint, magnetThreshold:16, snapToAngle). Модель Apple — Human Interface Guidelines (Drag and Drop) + UIDragInteraction/UIDropInteraction/NSItemProvider + WWDC-сессии по DnD/spring-loading. Демо — упрощённые иллюстрации механик, не их реальный код. Концепт/мнение, не аффилировано.
Связанное: TakeProfit, by Apple · весь ресёрч одной лентой · оглавление.