The Garage SDKs, in the open
garage_auth, garage_entitlements, garage_iap and garage_ui, moved out of Garage-Services and Metro-Map-Maker into one public repo. MIT, one readme, docs under docs/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013F4NWNvYcdeSgqbWMT1VQ7
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
import "package:flutter/widgets.dart";
|
||||
|
||||
import "menu.dart";
|
||||
|
||||
const double _kContextMenuGap = 8.0;
|
||||
const double _kContextMenuScreenPadding = 8.0;
|
||||
const double _kContextMenuMinWidth = 192.0;
|
||||
|
||||
// shows a right-click style context menu at [globalPosition]. used to lean on
|
||||
// shadcn's OverlayManager.showMenu; now its a plain flutter Overlay entry with
|
||||
// a transparent tap-catcher to dismiss.
|
||||
void showContextMenu({
|
||||
required BuildContext context,
|
||||
required Offset globalPosition,
|
||||
required List<MenuItem> items,
|
||||
required VoidCallback onDismissed,
|
||||
}) {
|
||||
final overlay = Overlay.of(context, rootOverlay: true);
|
||||
final overlayBox = overlay.context.findRenderObject() as RenderBox?;
|
||||
final overlayPosition =
|
||||
overlayBox?.globalToLocal(globalPosition) ?? globalPosition;
|
||||
|
||||
late OverlayEntry entry;
|
||||
var closed = false;
|
||||
void close() {
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
entry.remove();
|
||||
onDismissed();
|
||||
}
|
||||
|
||||
entry = OverlayEntry(
|
||||
builder: (ctx) {
|
||||
return Stack(
|
||||
children: [
|
||||
// tap anywhere outside to dismiss.
|
||||
Positioned.fill(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
excludeFromSemantics: true,
|
||||
onTap: close,
|
||||
child: const SizedBox.expand(),
|
||||
),
|
||||
),
|
||||
CustomSingleChildLayout(
|
||||
delegate: _ContextMenuLayoutDelegate(position: overlayPosition),
|
||||
child: MenuGroup(
|
||||
direction: Axis.vertical,
|
||||
subMenuOffset: const Offset(8, -4),
|
||||
onDismissed: close,
|
||||
builder: (ctx, children) => MenuPopup(children: children),
|
||||
children: items,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
overlay.insert(entry);
|
||||
}
|
||||
|
||||
class _ContextMenuLayoutDelegate extends SingleChildLayoutDelegate {
|
||||
const _ContextMenuLayoutDelegate({required this.position});
|
||||
|
||||
final Offset position;
|
||||
|
||||
@override
|
||||
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
|
||||
final maxWidth = (constraints.maxWidth - _kContextMenuScreenPadding * 2)
|
||||
.clamp(0.0, double.infinity);
|
||||
final maxHeight = (constraints.maxHeight - _kContextMenuScreenPadding * 2)
|
||||
.clamp(0.0, double.infinity);
|
||||
return BoxConstraints(
|
||||
minWidth: _kContextMenuMinWidth.clamp(0.0, maxWidth),
|
||||
maxWidth: maxWidth,
|
||||
maxHeight: maxHeight,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Offset getPositionForChild(Size size, Size childSize) {
|
||||
final minLeft = _kContextMenuScreenPadding;
|
||||
final minTop = _kContextMenuScreenPadding;
|
||||
final maxLeft = size.width - _kContextMenuScreenPadding - childSize.width;
|
||||
final maxTop = size.height - _kContextMenuScreenPadding - childSize.height;
|
||||
|
||||
final preferredRight = position.dx + _kContextMenuGap;
|
||||
final preferredLeft = position.dx - _kContextMenuGap - childSize.width;
|
||||
final rightFits =
|
||||
preferredRight + childSize.width <=
|
||||
size.width - _kContextMenuScreenPadding;
|
||||
final leftFits = preferredLeft >= _kContextMenuScreenPadding;
|
||||
final opensRight = rightFits || !leftFits;
|
||||
final rawLeft = opensRight ? preferredRight : preferredLeft;
|
||||
|
||||
final preferredBelow = position.dy;
|
||||
final preferredAbove = position.dy - childSize.height;
|
||||
final belowFits =
|
||||
preferredBelow + childSize.height <=
|
||||
size.height - _kContextMenuScreenPadding;
|
||||
final aboveFits = preferredAbove >= _kContextMenuScreenPadding;
|
||||
final opensBelow = belowFits || !aboveFits;
|
||||
final rawTop = opensBelow ? preferredBelow : preferredAbove;
|
||||
|
||||
return Offset(
|
||||
rawLeft.clamp(minLeft, maxLeft.clamp(minLeft, double.infinity)),
|
||||
rawTop.clamp(minTop, maxTop.clamp(minTop, double.infinity)),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRelayout(_ContextMenuLayoutDelegate oldDelegate) {
|
||||
return position != oldDelegate.position;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user