Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MenuBar, SubMenuButton and MenuItemButton controls #2252

Merged
merged 8 commits into from
Dec 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
32 changes: 29 additions & 3 deletions package/lib/src/controls/create_control.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import 'dart:math';

import 'package:collection/collection.dart';
import 'package:flet/src/controls/search_anchor.dart';
import 'package:flet/src/controls/segmented_button.dart';
import 'package:flutter/material.dart';

import 'package:collection/collection.dart';
import 'package:flutter_redux/flutter_redux.dart';

import '../flet_app_services.dart';
Expand All @@ -14,6 +13,7 @@ import '../models/page_media_view_model.dart';
import '../utils/animations.dart';
import '../utils/theme.dart';
import '../utils/transforms.dart';

import 'alert_dialog.dart';
import 'animated_switcher.dart';
import 'audio.dart';
Expand Down Expand Up @@ -59,6 +59,8 @@ import 'linechart.dart';
import 'list_tile.dart';
import 'list_view.dart';
import 'markdown.dart';
import 'menu_bar.dart';
import 'menu_item_button.dart';
import 'navigation_bar.dart';
import 'navigation_rail.dart';
import 'outlined_button.dart';
Expand All @@ -73,13 +75,16 @@ import 'range_slider.dart';
import 'responsive_row.dart';
import 'row.dart';
import 'safe_area.dart';
import 'search_anchor.dart';
import 'segmented_button.dart';
import 'selection_area.dart';
import 'semantics.dart';
import 'shader_mask.dart';
import 'shake_detector.dart';
import 'slider.dart';
import 'snack_bar.dart';
import 'stack.dart';
import 'submenu_button.dart';
import 'switch.dart';
import 'tabs.dart';
import 'text.dart';
Expand Down Expand Up @@ -324,6 +329,27 @@ Widget createWidget(Key? key, ControlViewModel controlView, Control? parent,
control: controlView.control,
children: controlView.children,
parentDisabled: parentDisabled);
case "menubar":
return MenuBarControl(
key: key,
parent: parent,
control: controlView.control,
children: controlView.children,
parentDisabled: parentDisabled);
case "submenubutton":
return SubMenuButtonControl(
key: key,
parent: parent,
control: controlView.control,
children: controlView.children,
parentDisabled: parentDisabled);
case "menuitembutton":
return MenuItemButtonControl(
key: key,
parent: parent,
control: controlView.control,
children: controlView.children,
parentDisabled: parentDisabled);
case "segmentedbutton":
return SegmentedButtonControl(
key: key,
Expand Down
6 changes: 2 additions & 4 deletions package/lib/src/controls/cupertino_navigation_bar.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'package:collection/collection.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
Expand All @@ -22,13 +21,12 @@ class CupertinoNavigationBarControl extends StatefulWidget {
final dynamic dispatch;

const CupertinoNavigationBarControl(
{Key? key,
{super.key,
this.parent,
required this.control,
required this.children,
required this.parentDisabled,
required this.dispatch})
: super(key: key);
required this.dispatch});

@override
State<CupertinoNavigationBarControl> createState() =>
Expand Down
80 changes: 1 addition & 79 deletions package/lib/src/controls/gesture_detector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:flutter/material.dart';

import '../flet_app_services.dart';
import '../models/control.dart';
import '../utils/mouse.dart';
import 'create_control.dart';
import 'error.dart';

Expand Down Expand Up @@ -584,85 +585,6 @@ class _GestureDetectorControlState extends State<GestureDetectorControl> {

return constrainedControl(context, result, widget.parent, widget.control);
}

MouseCursor parseMouseCursor(String? cursor) {
switch (cursor) {
case "alias":
return SystemMouseCursors.alias;
case "allScroll":
return SystemMouseCursors.allScroll;
case "basic":
return SystemMouseCursors.basic;
case "cell":
return SystemMouseCursors.cell;
case "click":
return SystemMouseCursors.click;
case "contextMenu":
return SystemMouseCursors.contextMenu;
case "copy":
return SystemMouseCursors.copy;
case "disappearing":
return SystemMouseCursors.disappearing;
case "forbidden":
return SystemMouseCursors.forbidden;
case "grab":
return SystemMouseCursors.grab;
case "grabbing":
return SystemMouseCursors.grabbing;
case "help":
return SystemMouseCursors.help;
case "move":
return SystemMouseCursors.move;
case "noDrop":
return SystemMouseCursors.noDrop;
case "none":
return SystemMouseCursors.none;
case "precise":
return SystemMouseCursors.precise;
case "progress":
return SystemMouseCursors.progress;
case "resizeColumn":
return SystemMouseCursors.resizeColumn;
case "resizeDown":
return SystemMouseCursors.resizeDown;
case "resizeDownLeft":
return SystemMouseCursors.resizeDownLeft;
case "resizeDownRight":
return SystemMouseCursors.resizeDownRight;
case "resizeLeft":
return SystemMouseCursors.resizeLeft;
case "resizeLeftRight":
return SystemMouseCursors.resizeLeftRight;
case "resizeRight":
return SystemMouseCursors.resizeRight;
case "resizeRow":
return SystemMouseCursors.resizeRow;
case "resizeUp":
return SystemMouseCursors.resizeUp;
case "resizeUpDown":
return SystemMouseCursors.resizeUpDown;
case "resizeUpLeft":
return SystemMouseCursors.resizeUpLeft;
case "resizeUpLeftDownRight":
return SystemMouseCursors.resizeUpLeftDownRight;
case "resizeUpRight":
return SystemMouseCursors.resizeUpRight;
case "resizeUpRightDownLeft":
return SystemMouseCursors.resizeUpRightDownLeft;
case "text":
return SystemMouseCursors.text;
case "verticalText":
return SystemMouseCursors.verticalText;
case "wait":
return SystemMouseCursors.wait;
case "zoomIn":
return SystemMouseCursors.zoomIn;
case "zoomOut":
return SystemMouseCursors.zoomOut;
default:
return MouseCursor.defer;
}
}
}

class MultiTouchGestureRecognizer extends MultiTapGestureRecognizer {
Expand Down
56 changes: 56 additions & 0 deletions package/lib/src/controls/menu_bar.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import 'package:flutter/material.dart';

import '../models/control.dart';
import '../utils/menu.dart';
import 'create_control.dart';
import 'error.dart';

class MenuBarControl extends StatefulWidget {
final Control? parent;
final Control control;
final List<Control> children;
final bool parentDisabled;

const MenuBarControl(
{super.key,
this.parent,
required this.control,
required this.children,
required this.parentDisabled});

@override
State<MenuBarControl> createState() => _MenuBarControlState();
}

class _MenuBarControlState extends State<MenuBarControl> {
@override
Widget build(BuildContext context) {
debugPrint("MenuBar build: ${widget.control.id}");

var ctrls = widget.children.where((c) => c.isVisible).toList();
if (ctrls.isEmpty) {
return const ErrorControl(
"MenuBar must have at least one child control",
);
}
bool disabled = widget.control.isDisabled || widget.parentDisabled;

var clipBehavior = Clip.values.firstWhere(
(e) =>
e.name.toLowerCase() ==
widget.control.attrString("clipBehavior", "")!.toLowerCase(),
orElse: () => Clip.none);

// var theme = Theme.of(context);

var style = parseMenuStyle(Theme.of(context), widget.control, "style");

MenuBar? menuBar = MenuBar(
style: style,
clipBehavior: clipBehavior,
children: ctrls.map((c) => createControl(widget.control, c.id, disabled)).toList(),
);

return constrainedControl(context, menuBar, widget.parent, widget.control);
}
}
134 changes: 134 additions & 0 deletions package/lib/src/controls/menu_item_button.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import 'package:flutter/material.dart';

import '../flet_app_services.dart';
import '../models/control.dart';
import '../utils/buttons.dart';
import 'create_control.dart';

class MenuItemButtonControl extends StatefulWidget {
final Control? parent;
final Control control;
final List<Control> children;
final bool parentDisabled;

const MenuItemButtonControl(
{super.key,
this.parent,
required this.control,
required this.children,
required this.parentDisabled});

@override
State<MenuItemButtonControl> createState() => _MenuItemButtonControlState();
}

class _MenuItemButtonControlState extends State<MenuItemButtonControl> {
late final FocusNode _focusNode;
String? _lastFocusValue;

@override
void initState() {
super.initState();
_focusNode = FocusNode();
_focusNode.addListener(_onFocusChange);
}

@override
void dispose() {
_focusNode.removeListener(_onFocusChange);
_focusNode.dispose();
super.dispose();
}

void _onFocusChange() {
FletAppServices.of(context).server.sendPageEvent(
eventTarget: widget.control.id,
eventName: _focusNode.hasFocus ? "focus" : "blur",
eventData: "");
}

@override
Widget build(BuildContext context) {
debugPrint("MenuItemButton build: ${widget.control.id}");
bool disabled = widget.control.isDisabled || widget.parentDisabled;

var content =
widget.children.where((c) => c.name == "content" && c.isVisible);
var leading =
widget.children.where((c) => c.name == "leading" && c.isVisible);
var trailing =
widget.children.where((c) => c.name == "trailing" && c.isVisible);

var clipBehavior = Clip.values.firstWhere(
(e) =>
e.name.toLowerCase() ==
widget.control.attrString("clipBehavior", "")!.toLowerCase(),
orElse: () => Clip.none);

var theme = Theme.of(context);
var style = parseButtonStyle(Theme.of(context), widget.control, "style",
defaultForegroundColor: theme.colorScheme.primary,
defaultBackgroundColor: Colors.transparent,
defaultOverlayColor: Colors.transparent,
defaultShadowColor: Colors.transparent,
defaultSurfaceTintColor: Colors.transparent,
defaultElevation: 0,
defaultPadding: const EdgeInsets.all(8),
defaultBorderSide: BorderSide.none,
defaultShape: theme.useMaterial3
? const StadiumBorder()
: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)));

bool onClick = widget.control.attrBool("onClick", false)!;
bool onHover = widget.control.attrBool("onHover", false)!;

var server = FletAppServices.of(context).server;

var menuItem = MenuItemButton(
focusNode: _focusNode,
clipBehavior: clipBehavior,
style: style,
closeOnActivate: widget.control.attrBool("closeOnClick", true)!,
requestFocusOnHover: widget.control.attrBool("focusOnHover", true)!,
onHover: onHover && !disabled
? (bool value) {
server.sendPageEvent(
eventTarget: widget.control.id,
eventName: "hover",
eventData: "$value");
}
: null,
onPressed: onClick && !disabled
? () {
server.sendPageEvent(
eventTarget: widget.control.id,
eventName: "click",
eventData: "");
}
: null,
leadingIcon: leading.isNotEmpty
? leading
.map((c) => createControl(widget.control, c.id, disabled))
.first
: null,
trailingIcon: trailing.isNotEmpty
? trailing
.map((c) => createControl(widget.control, c.id, disabled))
.first
: null,
child: content.isNotEmpty
? content
.map((c) => createControl(widget.control, c.id, disabled))
.first
: null,
);

var focusValue = widget.control.attrString("focus");
if (focusValue != null && focusValue != _lastFocusValue) {
_lastFocusValue = focusValue;
_focusNode.requestFocus();
}

return constrainedControl(context, menuItem, widget.parent, widget.control);
}
}
Loading