desktopCall function

Widget desktopCall(
  1. CallController c,
  2. BuildContext context
)

Returns a desktop design of a CallView.

Implementation

Widget desktopCall(CallController c, BuildContext context) {
  final style = Theme.of(context).style;

  return LayoutBuilder(
    builder: (context, constraints) {
      // Call stackable content.
      List<Widget> content = [
        const SvgImage.asset(
          'assets/images/background_dark.svg',
          width: double.infinity,
          height: double.infinity,
          fit: BoxFit.cover,
        ),
      ];

      // Secondary view possible alignment.
      Widget possibleContainer() {
        return Obx(() {
          Alignment? alignment = c.possibleSecondaryAlignment.value;
          if (alignment == null) {
            return Container();
          }

          double width = 10;
          double height = 10;

          if (alignment == Alignment.topCenter ||
              alignment == Alignment.bottomCenter) {
            width = double.infinity;
          } else {
            height = double.infinity;
          }

          return Align(
            alignment: alignment,
            child: ConditionalBackdropFilter(
              child: Container(
                height: height,
                width: width,
                color: ConditionalBackdropFilter.enabled
                    ? style.colors.primaryAuxiliaryOpacity25
                    : style.colors.primaryAuxiliaryOpacity90,
              ),
            ),
          );
        });
      }

      content.addAll([
        // Call's primary view.
        Column(
          children: [
            Obx(
              () => SizedBox(
                width: double.infinity,
                height:
                    c.secondary.isNotEmpty &&
                        c.secondaryAlignment.value == Alignment.topCenter
                    ? c.secondaryHeight.value
                    : 0,
              ),
            ),
            Expanded(
              child: Row(
                children: [
                  Obx(
                    () => SizedBox(
                      height: double.infinity,
                      width:
                          c.secondary.isNotEmpty &&
                              c.secondaryAlignment.value == Alignment.centerLeft
                          ? c.secondaryWidth.value
                          : 0,
                    ),
                  ),
                  Expanded(
                    child: Stack(
                      children: [
                        Obx(() {
                          final bool isOutgoing =
                              (c.outgoing ||
                                  c.state.value == OngoingCallState.local) &&
                              !c.started;

                          final bool isIncoming =
                              c.state.value != OngoingCallState.active &&
                              c.state.value != OngoingCallState.joining &&
                              !isOutgoing;

                          final Widget child;

                          if (!isIncoming) {
                            child = _primaryView(c);
                          } else {
                            if (c.isDialog) {
                              final RxUser? user = c.chat.value?.members.values
                                  .firstWhereOrNull(
                                    (e) => e.user.id != c.me.id.userId,
                                  )
                                  ?.user;

                              child = CallCoverWidget(
                                c.chat.value?.callCover,
                                user: user,
                              );
                            } else {
                              if (c.chat.value?.avatar.value != null) {
                                final Avatar avatar =
                                    c.chat.value!.avatar.value!;
                                child = CallCoverWidget(
                                  UserCallCover(
                                    full: avatar.full,
                                    original: avatar.original,
                                    square: avatar.full,
                                    vertical: avatar.full,
                                  ),
                                );
                              } else {
                                child = CallCoverWidget(
                                  null,
                                  chat: c.chat.value,
                                );
                              }
                            }
                          }

                          return SafeAnimatedSwitcher(
                            duration: 400.milliseconds,
                            child: child,
                          );
                        }),
                      ],
                    ),
                  ),
                  Obx(
                    () => SizedBox(
                      height: double.infinity,
                      width:
                          c.secondary.isNotEmpty &&
                              c.secondaryAlignment.value ==
                                  Alignment.centerRight
                          ? c.secondaryWidth.value
                          : 0,
                    ),
                  ),
                ],
              ),
            ),
            Obx(
              () => SizedBox(
                width: double.infinity,
                height:
                    c.secondary.isNotEmpty &&
                        c.secondaryAlignment.value == Alignment.bottomCenter
                    ? c.secondaryHeight.value
                    : 0,
              ),
            ),
          ],
        ),

        // Reconnection indicator.
        Obx(() {
          if (!c.connectionLost.value) {
            return const SizedBox();
          }

          return IgnorePointer(
            child: Container(
              width: double.infinity,
              height: double.infinity,
              color: style.colors.onBackgroundOpacity70,
              padding: const EdgeInsets.all(21.0),
              child: Center(
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    if (!Config.disableInfiniteAnimations)
                      const DoubleBounceLoadingIndicator(),
                    const SizedBox(height: 16),
                    Text(
                      'label_reconnecting_ellipsis'.l10n,
                      style: style.fonts.normal.regular.onPrimary,
                    ),
                  ],
                ),
              ),
            ),
          );
        }),

        possibleContainer(),

        // Makes UI appear on click.
        Listener(
          behavior: HitTestBehavior.translucent,
          onPointerDown: (d) {
            c.downPosition = d.localPosition;
            c.downButtons = d.buttons;
          },
          onPointerUp: (d) {
            if (c.downButtons & kPrimaryButton != 0 &&
                (d.localPosition.distanceSquared -
                            c.downPosition.distanceSquared)
                        .abs() <=
                    1500) {
              if (c.primaryDrags.value == 0 && c.secondaryDrags.value == 0) {
                if (c.state.value == OngoingCallState.active) {
                  if (!c.showUi.value) {
                    c.keepUi();
                  } else {
                    c.keepUi(false);
                  }
                }
              }
            }
          },
        ),

        // Empty drop zone if [secondary] is empty.
        Obx(() {
          final Axis secondaryAxis = c.size.width >= c.size.height
              ? Axis.horizontal
              : Axis.vertical;

          /// Pre-calculate the [ReorderableFit]'s size.
          final double panelSize = max(
            ReorderableFit.calculateSize(
              maxSize: c.size.shortestSide / 4,
              constraints: Size(c.size.width, c.size.height - 45),
              axis: c.size.width >= c.size.height
                  ? Axis.horizontal
                  : Axis.vertical,
              length: c.secondary.length,
            ),
            130,
          );

          return SafeAnimatedSwitcher(
            key: const Key('SecondaryTargetAnimatedSwitcher'),
            duration: 200.milliseconds,
            child: c.secondary.isEmpty && c.doughDraggedRenderer.value != null
                ? DropBoxArea<_DragData>(
                    size: panelSize,
                    axis: secondaryAxis,
                    visible: c.primaryDrags.value >= 1,
                    onWillAccept: (d) => d?.chatId == c.chatId.value,
                    onAccept: (_DragData d) {
                      if (secondaryAxis == Axis.horizontal) {
                        c.secondaryAlignment.value = Alignment.centerRight;
                      } else {
                        c.secondaryAlignment.value = Alignment.topCenter;
                      }

                      c.unfocus(d.participant);
                    },
                  )
                : const SizedBox(),
          );
        }),
      ]);

      // Builds the [Dock] containing the [CallController.buttons].
      Widget dock() {
        return Obx(() {
          final bool isOutgoing =
              (c.outgoing || c.state.value == OngoingCallState.local) &&
              !c.started;

          final bool showBottomUi =
              (c.showUi.isTrue ||
              c.draggedButton.value != null ||
              c.state.value != OngoingCallState.active ||
              (c.state.value == OngoingCallState.active &&
                  c.locals.isEmpty &&
                  c.remotes.isEmpty &&
                  c.focused.isEmpty &&
                  c.paneled.isEmpty));

          final bool answer =
              c.state.value != OngoingCallState.joining &&
              c.state.value != OngoingCallState.active &&
              !isOutgoing;

          final Widget child;

          if (answer) {
            child = Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                const SizedBox(width: 11),
                AcceptAudioButton(
                  c,
                  highlight: !c.withVideo,
                ).build(hinted: false),
                const SizedBox(width: 24),
                AcceptVideoButton(
                  c,
                  highlight: c.withVideo,
                ).build(hinted: false),
                const SizedBox(width: 24),
                DeclineButton(c).build(hinted: false),
                const SizedBox(width: 11),
              ],
            );
          } else {
            child = Dock<CallButton>(
              items: c.buttons,
              itemWidth: CallController.buttonSize,
              itemBuilder: (e) =>
                  e.build(hinted: c.draggedButton.value == null),
              onReorder: (buttons) {
                c.buttons.value = buttons;
                c.relocateSecondary();
              },
              onDragStarted: (b) {
                c.showDragAndDropButtonsHint = false;
                c.draggedButton.value = b;
                c.draggedFromDock = false;
              },
              onDragEnded: (_) => c.draggedButton.value = null,
              onLeave: (_) => c.displayMore.value = true,
              onWillAccept: (d) => d?.c == c,
            );
          }

          return DockDecorator(
            show: showBottomUi,
            dockKey: c.dockKey,
            onAnimation: () =>
                Future.delayed(Duration.zero, c.relocateSecondary),
            onEnter: (d) => c.keepUi(true),
            onHover: (d) => c.keepUi(true),
            onExit: c.showUi.value && !c.displayMore.value
                ? (d) => c.keepUi(false)
                : (d) => c.keepUi(),
            child: child,
          );
        });
      }

      // Builds the [Launchpad] panel containing the [CallController.panel].
      Widget launchpad() {
        return Obx(() {
          bool enabled =
              c.displayMore.isTrue &&
              c.primaryDrags.value == 0 &&
              c.secondaryDrags.value == 0;

          return Flexible(
            child: AnimatedOpacity(
              duration: const Duration(milliseconds: 150),
              opacity: c.displayMore.value ? 1 : 0,
              child: IgnorePointer(
                ignoring: !c.displayMore.value,
                child: Launchpad(
                  onEnter: enabled ? (d) => c.keepUi(true) : null,
                  onHover: enabled ? (d) => c.keepUi(true) : null,
                  onExit: enabled ? (d) => c.keepUi() : null,
                  onAccept: (CallButton data) {
                    if (!c.draggedFromDock) {
                      c.buttons.remove(data);
                      c.draggedButton.value = null;
                    }
                  },
                  onWillAccept: (CallButton? a) =>
                      a?.c == c && a?.isRemovable == true,
                  children: c.panel.map((e) {
                    return DelayedDraggable(
                      feedback: Transform.translate(
                        offset: const Offset(
                          CallController.buttonSize / 2 * -1,
                          CallController.buttonSize / 2 * -1,
                        ),
                        child: e.build(),
                      ),
                      data: e,
                      onDragStarted: () {
                        c.showDragAndDropButtonsHint = false;
                        c.draggedFromDock = true;
                        c.draggedButton.value = e;
                      },
                      onDragCompleted: () => c.draggedButton.value = null,
                      onDragEnd: (_) => c.draggedButton.value = null,
                      onDraggableCanceled: (_, __) =>
                          c.draggedButton.value = null,
                      maxSimultaneousDrags: e.isRemovable ? null : 0,
                      dragAnchorStrategy: pointerDragAnchorStrategy,
                      child: e.build(hinted: false, big: true, expanded: true),
                    );
                  }).toList(),
                ),
              ),
            ),
          );
        });
      }

      // Footer part of the call with buttons.
      List<Widget> footer = [
        // Animated bottom buttons.
        Align(
          alignment: Alignment.bottomCenter,
          child: Column(
            mainAxisSize: MainAxisSize.min,
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              Flexible(
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  verticalDirection: VerticalDirection.up,
                  children: [dock(), launchpad()],
                ),
              ),
            ],
          ),
        ),

        // Display the more hint, if not dismissed.
        Obx(() {
          return SafeAnimatedSwitcher(
            duration: 150.milliseconds,
            child: c.showDragAndDropButtonsHint && c.displayMore.value
                ? Align(
                    alignment: Alignment.bottomCenter,
                    child: Column(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        AnimatedDelayedSwitcher(
                          delay: const Duration(milliseconds: 500),
                          duration: const Duration(milliseconds: 200),
                          child: Align(
                            alignment: Alignment.topCenter,
                            child: Container(
                              width: 290,
                              padding: EdgeInsets.only(
                                top:
                                    10 +
                                    (WebUtils.isPopup
                                        ? 0
                                        : CallController.titleHeight),
                              ),
                              child: HintWidget(
                                text: 'label_hint_drag_n_drop_buttons'.l10n,
                                onTap: () =>
                                    c.showDragAndDropButtonsHint = false,
                              ),
                            ),
                          ),
                        ),
                        const Flexible(child: SizedBox(height: 420)),
                      ],
                    ),
                  )
                : Container(),
          );
        }),
      ];

      List<Widget> ui = [
        Obx(() {
          bool preferTitle = c.state.value != OngoingCallState.active;
          return GestureDetector(
            behavior: HitTestBehavior.translucent,
            onDoubleTap: c.toggleFullscreen,
            onPanUpdate: preferTitle
                ? (d) {
                    c.left.value = c.left.value + d.delta.dx;
                    c.top.value = c.top.value + d.delta.dy;
                    c.applyConstraints(context);
                  }
                : null,
          );
        }),

        // Sliding from the top title bar.
        Obx(() {
          final bool isOutgoing =
              (c.outgoing || c.state.value == OngoingCallState.local) &&
              !c.started;

          final bool preferTitle =
              c.state.value != OngoingCallState.active && !isOutgoing;

          return SafeAnimatedSwitcher(
            key: const Key('AnimatedSwitcherCallTitle'),
            duration: const Duration(milliseconds: 200),
            child: preferTitle
                ? Align(
                    key: const Key('CallTitlePadding'),
                    alignment: Alignment.topCenter,
                    child: Padding(
                      padding: EdgeInsets.only(
                        left: 10,
                        right: 10,
                        top: c.size.height * 0.05,
                      ),
                      child: callTitle(c),
                    ),
                  )
                : Container(key: UniqueKey()),
          );
        }),

        // Bottom [MouseRegion] that toggles UI on hover.
        Obx(() {
          final bool enabled =
              c.primaryDrags.value == 0 && c.secondaryDrags.value == 0;
          return Align(
            alignment: Alignment.bottomCenter,
            child: SizedBox(
              height: 100,
              width: double.infinity,
              child: MouseRegion(
                opaque: false,
                onEnter: enabled ? (d) => c.keepUi(true) : null,
                onHover: enabled ? (d) => c.keepUi(true) : null,
                onExit: c.showUi.value && enabled
                    ? (d) {
                        if (c.displayMore.isTrue) {
                          c.keepUi();
                        } else {
                          c.keepUi(false);
                        }
                      }
                    : null,
              ),
            ),
          );
        }),

        // Secondary panel itself.
        Obx(() {
          final bool isIncoming =
              c.state.value != OngoingCallState.active &&
              c.state.value != OngoingCallState.joining &&
              !(c.outgoing || c.state.value == OngoingCallState.local);

          if (isIncoming) {
            return const SizedBox();
          }

          return LayoutBuilder(
            builder: (_, constraints) {
              // Scale the secondary panel after this frame is displayed, as
              // otherwise it invokes re-drawing twice in a frame, resulting in an
              // error.
              WidgetsBinding.instance.addPostFrameCallback((_) {
                c.scaleSecondary(constraints);
                WidgetsBinding.instance.addPostFrameCallback(
                  (_) => c.relocateSecondary(),
                );
              });

              return _secondaryView(c, context);
            },
          );
        }),

        // [MouseRegion] changing the cursor.
        Obx(() {
          return MouseRegion(
            opaque: false,
            cursor:
                c.draggedRenderer.value != null ||
                    c.doughDraggedRenderer.value != null
                ? CustomMouseCursors.grabbing
                : c.isCursorHidden.value
                ? SystemMouseCursors.none
                : c.hoveredRenderer.value != null
                ? CustomMouseCursors.grab
                : c.hoveredParticipant.value != null
                ? SystemMouseCursors.basic
                : MouseCursor.defer,
          );
        }),

        // Top [MouseRegion] that toggles info header on hover.
        Align(
          alignment: Alignment.topCenter,
          child: SizedBox(
            height: 100,
            width: double.infinity,
            child: MouseRegion(
              opaque: false,
              onEnter: (_) {
                c.showHeader.value = true;
                c.isCursorHidden.value = false;
              },
              onHover: (_) {
                c.showHeader.value = true;
                c.isCursorHidden.value = false;
              },
              onExit: (_) {
                c.showHeader.value = false;
              },
            ),
          ),
        ),

        // Show a hint if any renderer is draggable.
        Obx(() {
          final bool hideSecondary = c.size.width < 500 && c.size.height < 500;
          final bool mayDragVideo =
              !hideSecondary &&
              (c.focused.length > 1 ||
                  (c.focused.isEmpty &&
                      c.primary.length + c.secondary.length > 1));

          return SafeAnimatedSwitcher(
            duration: 150.milliseconds,
            child: c.showDragAndDropVideosHint && mayDragVideo
                ? Padding(
                    padding: EdgeInsets.only(
                      top:
                          c.secondary.isNotEmpty &&
                              c.secondaryAlignment.value == Alignment.topCenter
                          ? 10 + c.secondaryHeight.value
                          : 10,
                      right:
                          c.secondary.isNotEmpty &&
                              c.secondaryAlignment.value ==
                                  Alignment.centerRight
                          ? 10 + c.secondaryWidth.value
                          : 10,
                    ),
                    child: Align(
                      alignment: Alignment.topRight,
                      child: SizedBox(
                        width: 320,
                        child: HintWidget(
                          text: 'label_hint_drag_n_drop_video'.l10n,
                          onTap: () => c.showDragAndDropVideosHint = false,
                        ),
                      ),
                    ),
                  )
                : const SizedBox(),
          );
        }),

        // Sliding from the top info header.
        if (WebUtils.isPopup)
          Obx(() {
            return Align(
              alignment: Alignment.topCenter,
              child: AnimatedSlider(
                duration: 400.milliseconds,
                translate: false,
                beginOffset: const Offset(0, -1),
                endOffset: const Offset(0, 0),
                isOpen:
                    c.state.value == OngoingCallState.active &&
                    c.showHeader.value,
                child: MouseRegion(
                  onEnter: (_) {
                    c.showHeader.value = true;
                    c.headerHovered = true;
                  },
                  onHover: (_) {
                    c.showHeader.value = true;
                    c.headerHovered = true;
                  },
                  onExit: (_) {
                    c.showHeader.value = false;
                    c.headerHovered = false;
                  },
                  child: Container(
                    decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(11),
                      boxShadow: [
                        CustomBoxShadow(
                          color: style.colors.onBackgroundOpacity20,
                          blurRadius: 8,
                          blurStyle: BlurStyle.outer,
                        ),
                      ],
                    ),
                    margin: const EdgeInsets.fromLTRB(10, 5, 10, 2),
                    child: ConditionalBackdropFilter(
                      borderRadius: BorderRadius.circular(11),
                      filter: ImageFilter.blur(sigmaX: 15, sigmaY: 15),
                      child: Container(
                        decoration: BoxDecoration(
                          color: ConditionalBackdropFilter.enabled
                              ? style.colors.primaryAuxiliaryOpacity25
                              : style.colors.primaryAuxiliaryOpacity90,
                          borderRadius: BorderRadius.circular(11),
                        ),
                        padding: const EdgeInsets.symmetric(
                          vertical: 8,
                          horizontal: 10,
                        ),
                        child: Row(
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            if (c.fullscreen.value) ...[
                              Text(
                                'label_call_title'.l10nfmt(c.titleArguments),
                                style: style.fonts.small.regular.onPrimary,
                                overflow: TextOverflow.ellipsis,
                              ),
                              Container(
                                margin: const EdgeInsets.fromLTRB(12, 0, 12, 0),
                                color: style.colors.onPrimary,
                                width: 1,
                                height: 12,
                              ),
                            ],
                            AnimatedButton(
                              enabled: c.draggedRenderer.value == null,
                              onPressed: c.layoutAsPrimary,
                              child: const SvgIcon(SvgIcons.callGallery),
                            ),
                            const SizedBox(width: 16),
                            AnimatedButton(
                              enabled: c.draggedRenderer.value == null,
                              onPressed: () =>
                                  c.layoutAsSecondary(floating: true),
                              child: const SvgIcon(SvgIcons.callFloating),
                            ),
                            const SizedBox(width: 16),
                            AnimatedButton(
                              enabled: c.draggedRenderer.value == null,
                              onPressed: () =>
                                  c.layoutAsSecondary(floating: false),
                              child: const SvgIcon(SvgIcons.callSide),
                            ),
                            const SizedBox(width: 16),
                            AnimatedButton(
                              enabled: c.draggedRenderer.value == null,
                              onPressed: c.toggleFullscreen,
                              child: SvgIcon(
                                c.fullscreen.value
                                    ? SvgIcons.fullscreenExitSmall
                                    : SvgIcons.fullscreenEnterSmall,
                              ),
                            ),
                            if (c.fullscreen.value) const SizedBox(width: 4),
                          ],
                        ),
                      ),
                    ),
                  ),
                ),
              ),
            );
          }),

        // If there's any notifications to show, display them.
        Align(
          alignment: Alignment.topCenter,
          child: Padding(
            padding: const EdgeInsets.only(top: 8),
            child: Obx(() {
              if (c.notifications.isEmpty) {
                return const SizedBox();
              }

              return Column(
                mainAxisSize: MainAxisSize.min,
                children: c.notifications.reversed.take(3).map((e) {
                  return CallNotificationWidget(
                    e,
                    onClose: () => c.notifications.remove(e),
                  );
                }).toList(),
              );
            }),
          ),
        ),

        Obx(() {
          if (c.minimized.value && !c.fullscreen.value) {
            return Container();
          }

          return Stack(children: footer);
        }),
      ];

      // Combines all the stackable content into [Scaffold].
      Widget scaffold = Scaffold(
        backgroundColor: style.colors.onBackground,
        body: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            if (!WebUtils.isPopup)
              GestureDetector(
                behavior: HitTestBehavior.translucent,
                onPanUpdate: (d) {
                  c.left.value = c.left.value + d.delta.dx;
                  c.top.value = c.top.value + d.delta.dy;
                  c.applyConstraints(context);
                },
                child: Container(
                  decoration: BoxDecoration(
                    color: style.colors.transparent,
                    borderRadius: BorderRadius.circular(30),
                    boxShadow: [
                      CustomBoxShadow(
                        color: style.colors.onBackgroundOpacity20,
                        blurRadius: 8,
                        blurStyle: BlurStyle.outer,
                      ),
                    ],
                  ),
                  child: TitleBar(
                    titleBuilder: (_) {
                      return Obx(() {
                        return Text(
                          'label_call_title'.l10nfmt(c.titleArguments),
                        );
                      });
                    },
                    chat: c.chat.value,
                    fullscreen: c.fullscreen.value,
                    height: CallController.titleHeight,
                    toggleFullscreen: c.draggedRenderer.value == null
                        ? c.toggleFullscreen
                        : null,
                    onPrimary: c.draggedRenderer.value == null
                        ? c.layoutAsPrimary
                        : null,
                    onFloating: c.draggedRenderer.value == null
                        ? () => c.layoutAsSecondary(floating: true)
                        : null,
                    onSecondary: c.draggedRenderer.value == null
                        ? () => c.layoutAsSecondary(floating: false)
                        : null,
                  ),
                ),
              ),
            Expanded(child: Stack(children: [...content, ...ui])),
          ],
        ),
      );

      if (c.minimized.value && !c.fullscreen.value) {
        // Applies constraints on every rebuild.
        // This includes the screen size changes.
        c.applyConstraints(context);

        if (c.hidden.value) {
          return const SizedBox();
        }

        // Returns a [Scaler] scaling the minimized view.
        Widget scaler({
          Key? key,
          MouseCursor cursor = MouseCursor.defer,
          required Function(double, double) onDrag,
          double? width,
          double? height,
        }) {
          return Obx(() {
            return MouseRegion(
              cursor: c.draggedRenderer.value != null
                  ? MouseCursor.defer
                  : cursor,
              child: Scaler(
                key: key,
                onDragUpdate: onDrag,
                onDragEnd: (_) => c.updateSecondaryAttach(),
                width: width ?? Scaler.size,
                height: height ?? Scaler.size,
              ),
            );
          });
        }

        // Returns a stack of draggable [Scaler]s on each of the sides:
        //
        // +-------+
        // |       |
        // |       |
        // |       |
        // +-------+
        //
        // 1) + is a cornered scale point;
        // 2) | is a horizontal scale point;
        // 3) - is a vertical scale point.
        return Stack(
          children: [
            // Top middle.
            Obx(() {
              return Positioned(
                top: c.top.value - Scaler.size / 2,
                left: c.left.value + Scaler.size / 2,
                child: scaler(
                  cursor: SystemMouseCursors.resizeUpDown,
                  width: c.width.value - Scaler.size,
                  onDrag: (dx, dy) =>
                      c.resize(context, y: ScaleModeY.top, dy: dy),
                ),
              );
            }),

            // Center left.
            Obx(() {
              return Positioned(
                top: c.top.value + Scaler.size / 2,
                left: c.left.value - Scaler.size / 2,
                child: scaler(
                  cursor: SystemMouseCursors.resizeLeftRight,
                  height: c.height.value - Scaler.size,
                  onDrag: (dx, dy) =>
                      c.resize(context, x: ScaleModeX.left, dx: dx),
                ),
              );
            }),

            // Center right.
            Obx(() {
              return Positioned(
                top: c.top.value + Scaler.size / 2,
                left: c.left.value + c.width.value - Scaler.size / 2,
                child: scaler(
                  cursor: SystemMouseCursors.resizeLeftRight,
                  height: c.height.value - Scaler.size,
                  onDrag: (dx, dy) =>
                      c.resize(context, x: ScaleModeX.right, dx: -dx),
                ),
              );
            }),

            // Bottom center.
            Obx(() {
              return Positioned(
                top: c.top.value + c.height.value - Scaler.size / 2,
                left: c.left.value + Scaler.size / 2,
                child: scaler(
                  cursor: SystemMouseCursors.resizeUpDown,
                  width: c.width.value - Scaler.size,
                  onDrag: (dx, dy) =>
                      c.resize(context, y: ScaleModeY.bottom, dy: -dy),
                ),
              );
            }),

            // Top left.
            Obx(() {
              return Positioned(
                top: c.top.value - Scaler.size / 2,
                left: c.left.value - Scaler.size / 2,
                child: scaler(
                  cursor: CustomMouseCursors.resizeUpLeftDownRight,
                  width: Scaler.size * 2,
                  height: Scaler.size * 2,
                  onDrag: (dx, dy) => c.resize(
                    context,
                    y: ScaleModeY.top,
                    x: ScaleModeX.left,
                    dx: dx,
                    dy: dy,
                  ),
                ),
              );
            }),

            // Top right.
            Obx(() {
              return Positioned(
                top: c.top.value - Scaler.size / 2,
                left: c.left.value + c.width.value - 3 * Scaler.size / 2,
                child: scaler(
                  cursor: CustomMouseCursors.resizeUpRightDownLeft,
                  width: Scaler.size * 2,
                  height: Scaler.size * 2,
                  onDrag: (dx, dy) => c.resize(
                    context,
                    y: ScaleModeY.top,
                    x: ScaleModeX.right,
                    dx: -dx,
                    dy: dy,
                  ),
                ),
              );
            }),

            // Bottom left.
            Obx(() {
              return Positioned(
                top: c.top.value + c.height.value - 3 * Scaler.size / 2,
                left: c.left.value - Scaler.size / 2,
                child: scaler(
                  cursor: CustomMouseCursors.resizeUpRightDownLeft,
                  width: Scaler.size * 2,
                  height: Scaler.size * 2,
                  onDrag: (dx, dy) => c.resize(
                    context,
                    y: ScaleModeY.bottom,
                    x: ScaleModeX.left,
                    dx: dx,
                    dy: -dy,
                  ),
                ),
              );
            }),

            // Bottom right.
            Obx(() {
              return Positioned(
                top: c.top.value + c.height.value - 3 * Scaler.size / 2,
                left: c.left.value + c.width.value - 3 * Scaler.size / 2,
                child: scaler(
                  cursor: CustomMouseCursors.resizeUpLeftDownRight,
                  width: Scaler.size * 2,
                  height: Scaler.size * 2,
                  onDrag: (dx, dy) => c.resize(
                    context,
                    y: ScaleModeY.bottom,
                    x: ScaleModeX.right,
                    dx: -dx,
                    dy: -dy,
                  ),
                ),
              );
            }),

            Obx(() {
              return Positioned(
                left: c.left.value,
                top: c.top.value,
                width: c.width.value,
                height: c.height.value,
                child: Material(
                  type: MaterialType.card,
                  borderRadius: BorderRadius.circular(10),
                  elevation: 10,
                  child: Stack(
                    children: [
                      ClipRRect(
                        borderRadius: BorderRadius.circular(10),
                        child: scaffold,
                      ),
                      ClipRect(child: Stack(children: footer)),
                    ],
                  ),
                ),
              );
            }),
          ],
        );
      }

      // If the call popup is not [minimized], then return the [scaffold].
      return scaffold;
    },
  );
}