Skip to content

Commit

Permalink
Merge pull request #12 from chesnoksatan/context-menu
Browse files Browse the repository at this point in the history
Context menu features
  • Loading branch information
HrX03 authored Jul 14, 2022
2 parents 94249d4 + 726decb commit a613958
Show file tree
Hide file tree
Showing 10 changed files with 531 additions and 206 deletions.
9 changes: 9 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import 'dart:async';
import 'package:animations/animations.dart';
import 'package:files/backend/providers.dart';
import 'package:files/backend/utils.dart';
import 'package:files/widgets/context_menu/context_menu_theme.dart';
import 'package:files/widgets/side_pane.dart';
import 'package:files/widgets/tab_strip.dart';
import 'package:files/widgets/workspace.dart';
Expand Down Expand Up @@ -67,6 +68,9 @@ class Files extends StatelessWidget {
mainAxisMargin: 0,
radius: Radius.zero,
),
extensions: [
ContextMenuTheme(),
],
),
scrollBehavior: const MaterialScrollBehavior().copyWith(
scrollbars: false,
Expand Down Expand Up @@ -116,6 +120,11 @@ class _FilesHomeState extends State<FilesHome> {
SidePane(
destinations: sideDestinations,
workspace: workspaces[currentWorkspace],
onNewTab: (String tabPath) {
workspaces.add(WorkspaceController(initialDir: tabPath));
currentWorkspace = workspaces.length - 1;
setState(() {});
},
),
Expanded(
child: Material(
Expand Down
137 changes: 0 additions & 137 deletions lib/widgets/context_menu.dart

This file was deleted.

69 changes: 69 additions & 0 deletions lib/widgets/context_menu/context_menu.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import 'package:files/widgets/context_menu/context_menu_entry.dart';
import 'package:files/widgets/context_menu/context_menu_theme.dart';
import 'package:flutter/material.dart';

/// [ContextMenu] provides popup menu for the [child] widget and contains [entries].
class ContextMenu extends StatelessWidget {
/// [child] is the widget for which the context menu will be called.
final Widget child;

/// The [entries] are displayed in the order they are provided.
/// They can be [ContextMenuEntry], [ContextMenuDivider], [ContextSubMenuEntry] or any other widgets inherited from PopupMenuEntry.
final List<BaseContextMenuEntry> entries;

/// Whether the context menu will be displayed when tapped on a secondary button.
/// This defaults to true.
final bool openOnSecondary;

/// Whether the context menu will be displayed when a long press gesture with a primary button has been recognized.
/// This defaults to true.
final bool openOnLong;

const ContextMenu({
required this.child,
required this.entries,
this.openOnSecondary = true,
this.openOnLong = true,
Key? key,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return GestureDetector(
onLongPressStart: openOnLong
? (details) =>
openContextMenu(context, details.globalPosition, entries)
: null,
onSecondaryTapUp: openOnSecondary
? (details) =>
openContextMenu(context, details.globalPosition, entries)
: null,
behavior: HitTestBehavior.translucent,
child: child,
);
}
}

/// Show a popup menu that contains the [entries] at [position].
/// Can be used without [ContextMenu] widget
void openContextMenu(
BuildContext context,
Offset position,
List<BaseContextMenuEntry> entries,
) {
final ContextMenuTheme menuTheme =
Theme.of(context).extension<ContextMenuTheme>()!;

showMenu(
context: context,
position: RelativeRect.fromLTRB(
position.dx,
position.dy,
position.dx,
position.dy,
),
items: entries,
color: menuTheme.backgroundColor,
shape: menuTheme.shape,
);
}
142 changes: 142 additions & 0 deletions lib/widgets/context_menu/context_menu_entry.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import 'package:files/widgets/context_menu/context_menu_theme.dart';
import 'package:files/widgets/context_menu/context_sub_menu_entry.dart';
import 'package:flutter/material.dart';

abstract class BaseContextMenuEntry extends PopupMenuEntry<String> {
/// Using for [represents] method.
final String id;

/// A widget to display before the title.
/// Typically a [Icon] widget.
final Widget? leading;

/// The primary content of the menu entry.
/// Typically a [Text] widget.
final Widget title;

/// bool flag to block gestures from user
final bool enabled;

const BaseContextMenuEntry({
required this.id,
required this.title,
this.leading,
this.enabled = true,
Key? key,
}) : super(key: key);

@override
double get height => 48;

@override
bool represents(String? value) => id == value;
}

/// [ContextSubMenuEntry] is a [PopupMenuEntry] that displays a base menu entry.
class ContextMenuEntry extends BaseContextMenuEntry {
/// A tap with a primary button has occurred.
final VoidCallback onTap;

/// Optional content to display keysequence after the title.
/// Typically a [Text] widget.
final Widget? shortcut;

const ContextMenuEntry({
required String id,
required Widget title,
required this.onTap,
Widget? leading,
this.shortcut,
bool enabled = true,
Key? key,
}) : super(
id: id,
leading: leading,
title: title,
enabled: enabled,
key: key,
);

@override
_ContextMenuEntryState createState() => _ContextMenuEntryState();
}

class _ContextMenuEntryState extends State<ContextMenuEntry> {
@override
Widget build(BuildContext context) {
final ContextMenuTheme menuTheme =
Theme.of(context).extension<ContextMenuTheme>()!;

return InkWell(
onTap: widget.enabled
? () {
Navigator.pop(context);
widget.onTap.call();
}
: null,
child: Container(
height: widget.height,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
if (widget.leading != null) ...[
IconTheme.merge(
data: IconThemeData(
size: menuTheme.iconSize,
color: widget.enabled
? menuTheme.iconColor
: menuTheme.disabledIconColor,
),
child: widget.leading!,
),
const SizedBox(width: 16),
],
Expanded(
child: DefaultTextStyle(
style: TextStyle(
fontSize: menuTheme.fontSize,
color: widget.enabled
? menuTheme.textColor
: menuTheme.disabledTextColor,
),
overflow: TextOverflow.ellipsis,
child: widget.title,
),
),
if (widget.shortcut != null)
DefaultTextStyle(
style: TextStyle(
fontSize: menuTheme.fontSize,
color: widget.enabled
? menuTheme.shortcutColor
: menuTheme.disabledShortcutColor,
),
overflow: TextOverflow.ellipsis,
child: widget.shortcut!,
),
],
),
),
);
}
}

/// A horizontal divider in a Material Design popup menu.
class ContextMenuDivider extends BaseContextMenuEntry {
const ContextMenuDivider({Key? key})
: super(id: "", title: const SizedBox(), key: key);

@override
bool represents(void value) => false;

@override
double get height => 8;

@override
State<ContextMenuDivider> createState() => _ContextMenuDividerState();
}

class _ContextMenuDividerState extends State<ContextMenuDivider> {
@override
Widget build(BuildContext context) => Divider(height: widget.height);
}
Loading

0 comments on commit a613958

Please sign in to comment.