Made build portable

This commit is contained in:
Alessandro Autiero
2022-10-07 17:23:30 +02:00
parent 000a2a53ed
commit 55467152c9
128 changed files with 54402 additions and 7 deletions

View File

@@ -0,0 +1,5 @@
export 'src/widgets/window_border.dart';
export 'src/widgets/window_button.dart';
export 'src/widgets/window_caption.dart';
export 'src/icons/icons.dart';
export 'src/app_window.dart';

View File

@@ -0,0 +1,43 @@
import 'package:bitsdojo_window_platform_interface/bitsdojo_window_platform_interface.dart';
import 'package:bitsdojo_window_platform_interface/method_channel_bitsdojo_window.dart';
import 'package:bitsdojo_window_windows/bitsdojo_window_windows.dart';
import 'package:bitsdojo_window_macos/bitsdojo_window_macos.dart';
import 'package:bitsdojo_window_linux/bitsdojo_window_linux.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'dart:io' show Platform;
bool _platformInstanceNeedsInit = true;
void initPlatformInstance() {
if (!kIsWeb) {
if (BitsdojoWindowPlatform.instance is MethodChannelBitsdojoWindow) {
if (Platform.isWindows) {
BitsdojoWindowPlatform.instance = BitsdojoWindowWindows();
} else if (Platform.isMacOS) {
BitsdojoWindowPlatform.instance = BitsdojoWindowMacOS();
} else if (Platform.isLinux) {
BitsdojoWindowPlatform.instance = BitsdojoWindowLinux();
}
}
} else {
BitsdojoWindowPlatform.instance = BitsdojoWindowPlatformNotImplemented();
}
}
BitsdojoWindowPlatform get _platform {
var needsInit = _platformInstanceNeedsInit;
if (needsInit) {
initPlatformInstance();
_platformInstanceNeedsInit = false;
}
return BitsdojoWindowPlatform.instance;
}
void doWhenWindowReady(VoidCallback callback) {
_platform.doWhenWindowReady(callback);
}
DesktopWindow get appWindow {
return _platform.appWindow;
}

View File

@@ -0,0 +1,114 @@
import 'dart:math';
import 'package:flutter/widgets.dart';
// Switched to CustomPaint icons by https://github.com/esDotDev
/// Close
class CloseIcon extends StatelessWidget {
final Color color;
CloseIcon({Key? key, required this.color}) : super(key: key);
@override
Widget build(BuildContext context) => Align(
alignment: Alignment.topLeft,
child: Stack(children: [
// Use rotated containers instead of a painter because it renders slightly crisper than a painter for some reason.
Transform.rotate(
angle: pi * .25,
child:
Center(child: Container(width: 14, height: 1, color: color))),
Transform.rotate(
angle: pi * -.25,
child:
Center(child: Container(width: 14, height: 1, color: color))),
]),
);
}
/// Maximize
class MaximizeIcon extends StatelessWidget {
final Color color;
MaximizeIcon({Key? key, required this.color}) : super(key: key);
@override
Widget build(BuildContext context) => _AlignedPaint(_MaximizePainter(color));
}
class _MaximizePainter extends _IconPainter {
_MaximizePainter(Color color) : super(color);
@override
void paint(Canvas canvas, Size size) {
Paint p = getPaint(color);
canvas.drawRect(Rect.fromLTRB(0, 0, size.width - 1, size.height - 1), p);
}
}
/// Restore
class RestoreIcon extends StatelessWidget {
final Color color;
RestoreIcon({
Key? key,
required this.color,
}) : super(key: key);
@override
Widget build(BuildContext context) => _AlignedPaint(_RestorePainter(color));
}
class _RestorePainter extends _IconPainter {
_RestorePainter(Color color) : super(color);
@override
void paint(Canvas canvas, Size size) {
Paint p = getPaint(color);
canvas.drawRect(Rect.fromLTRB(0, 2, size.width - 2, size.height), p);
canvas.drawLine(Offset(2, 2), Offset(2, 0), p);
canvas.drawLine(Offset(2, 0), Offset(size.width, 0), p);
canvas.drawLine(
Offset(size.width, 0), Offset(size.width, size.height - 2), p);
canvas.drawLine(Offset(size.width, size.height - 2),
Offset(size.width - 2, size.height - 2), p);
}
}
/// Minimize
class MinimizeIcon extends StatelessWidget {
final Color color;
MinimizeIcon({Key? key, required this.color}) : super(key: key);
@override
Widget build(BuildContext context) => _AlignedPaint(_MinimizePainter(color));
}
class _MinimizePainter extends _IconPainter {
_MinimizePainter(Color color) : super(color);
@override
void paint(Canvas canvas, Size size) {
Paint p = getPaint(color);
canvas.drawLine(
Offset(0, size.height / 2), Offset(size.width, size.height / 2), p);
}
}
/// Helpers
abstract class _IconPainter extends CustomPainter {
_IconPainter(this.color);
final Color color;
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
class _AlignedPaint extends StatelessWidget {
const _AlignedPaint(this.painter, {Key? key}) : super(key: key);
final CustomPainter painter;
@override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.center,
child: CustomPaint(size: Size(10, 10), painter: painter));
}
}
Paint getPaint(Color color, [bool isAntiAlias = false]) => Paint()
..color = color
..style = PaintingStyle.stroke
..isAntiAlias = isAntiAlias
..strokeWidth = 1;

View File

@@ -0,0 +1,71 @@
import 'package:flutter/widgets.dart';
typedef MouseStateBuilderCB = Widget Function(
BuildContext context, MouseState mouseState);
class MouseState {
bool isMouseOver = false;
bool isMouseDown = false;
MouseState();
@override
String toString() {
return "isMouseDown: ${this.isMouseDown} - isMouseOver: ${this.isMouseOver}";
}
}
T? _ambiguate<T>(T? value) => value;
class MouseStateBuilder extends StatefulWidget {
final MouseStateBuilderCB builder;
final VoidCallback? onPressed;
MouseStateBuilder({Key? key, required this.builder, this.onPressed})
: super(key: key);
@override
_MouseStateBuilderState createState() => _MouseStateBuilderState();
}
class _MouseStateBuilderState extends State<MouseStateBuilder> {
late MouseState _mouseState;
_MouseStateBuilderState() {
_mouseState = MouseState();
}
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (event) {
setState(() {
_mouseState.isMouseOver = true;
});
},
onExit: (event) {
setState(() {
_mouseState.isMouseOver = false;
});
},
child: GestureDetector(
onTapDown: (_) {
setState(() {
_mouseState.isMouseDown = true;
});
},
onTapCancel: () {
setState(() {
_mouseState.isMouseDown = false;
});
},
onTap: () {
setState(() {
_mouseState.isMouseDown = false;
_mouseState.isMouseOver = false;
});
_ambiguate(WidgetsBinding.instance)!.addPostFrameCallback((_) {
if (widget.onPressed != null) {
widget.onPressed!();
}
});
},
onTapUp: (_) {},
child: widget.builder(context, _mouseState)));
}
}

View File

@@ -0,0 +1,49 @@
import 'package:bitsdojo_window_windows/bitsdojo_window_windows.dart'
show WinDesktopWindow;
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import '../app_window.dart';
class WindowBorder extends StatelessWidget {
final Widget child;
final Color color;
final double? width;
WindowBorder({Key? key, required this.child, required this.color, this.width})
: super(key: key);
@override
Widget build(BuildContext context) {
bool isWindowsApp =
(!kIsWeb) && (defaultTargetPlatform == TargetPlatform.windows);
bool isLinuxApp =
(!kIsWeb) && (defaultTargetPlatform == TargetPlatform.linux);
// Only show border on Windows and Linux
if (!(isWindowsApp || isLinuxApp)) {
return child;
}
var borderWidth = width ?? 1;
var topBorderWidth = width ?? 1;
if (appWindow is WinDesktopWindow) {
appWindow as WinDesktopWindow..setWindowCutOnMaximize(borderWidth.ceil());
}
if (isWindowsApp) {
topBorderWidth += 1 / appWindow.scaleFactor;
}
final topBorderSide = BorderSide(color: this.color, width: topBorderWidth);
final borderSide = BorderSide(color: this.color, width: borderWidth);
return Container(
child: child,
decoration: BoxDecoration(
border: Border(
top: topBorderSide,
left: borderSide,
right: borderSide,
bottom: borderSide)));
}
}

View File

@@ -0,0 +1,203 @@
import 'package:flutter/material.dart';
import './mouse_state_builder.dart';
import '../icons/icons.dart';
import '../app_window.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'dart:io' show Platform;
typedef WindowButtonIconBuilder = Widget Function(
WindowButtonContext buttonContext);
typedef WindowButtonBuilder = Widget Function(
WindowButtonContext buttonContext, Widget icon);
class WindowButtonContext {
BuildContext context;
MouseState mouseState;
Color? backgroundColor;
Color iconColor;
WindowButtonContext(
{required this.context,
required this.mouseState,
this.backgroundColor,
required this.iconColor});
}
class WindowButtonColors {
late Color normal;
late Color mouseOver;
late Color mouseDown;
late Color iconNormal;
late Color iconMouseOver;
late Color iconMouseDown;
WindowButtonColors(
{Color? normal,
Color? mouseOver,
Color? mouseDown,
Color? iconNormal,
Color? iconMouseOver,
Color? iconMouseDown}) {
this.normal = normal ?? _defaultButtonColors.normal;
this.mouseOver = mouseOver ?? _defaultButtonColors.mouseOver;
this.mouseDown = mouseDown ?? _defaultButtonColors.mouseDown;
this.iconNormal = iconNormal ?? _defaultButtonColors.iconNormal;
this.iconMouseOver = iconMouseOver ?? _defaultButtonColors.iconMouseOver;
this.iconMouseDown = iconMouseDown ?? _defaultButtonColors.iconMouseDown;
}
}
final _defaultButtonColors = WindowButtonColors(
normal: Colors.transparent,
iconNormal: Color(0xFF805306),
mouseOver: Color(0xFF404040),
mouseDown: Color(0xFF202020),
iconMouseOver: Color(0xFFFFFFFF),
iconMouseDown: Color(0xFFF0F0F0));
class WindowButton extends StatelessWidget {
final WindowButtonBuilder? builder;
final WindowButtonIconBuilder? iconBuilder;
late final WindowButtonColors colors;
final bool animate;
final EdgeInsets? padding;
final VoidCallback? onPressed;
WindowButton(
{Key? key,
WindowButtonColors? colors,
this.builder,
@required this.iconBuilder,
this.padding,
this.onPressed,
this.animate = false})
: super(key: key) {
this.colors = colors ?? _defaultButtonColors;
}
Color getBackgroundColor(MouseState mouseState) {
if (mouseState.isMouseDown) return colors.mouseDown;
if (mouseState.isMouseOver) return colors.mouseOver;
return colors.normal;
}
Color getIconColor(MouseState mouseState) {
if (mouseState.isMouseDown) return colors.iconMouseDown;
if (mouseState.isMouseOver) return colors.iconMouseOver;
return colors.iconNormal;
}
@override
Widget build(BuildContext context) {
if (kIsWeb) {
return Container();
} else {
// Don't show button on macOS
if (Platform.isMacOS) {
return Container();
}
}
final buttonSize = appWindow.titleBarButtonSize;
return MouseStateBuilder(
builder: (context, mouseState) {
WindowButtonContext buttonContext = WindowButtonContext(
mouseState: mouseState,
context: context,
backgroundColor: getBackgroundColor(mouseState),
iconColor: getIconColor(mouseState));
var icon = (this.iconBuilder != null)
? this.iconBuilder!(buttonContext)
: Container();
double borderSize = appWindow.borderSize;
double defaultPadding =
(appWindow.titleBarHeight - borderSize) / 3 - (borderSize / 2);
// Used when buttonContext.backgroundColor is null, allowing the AnimatedContainer to fade-out smoothly.
var fadeOutColor =
getBackgroundColor(MouseState()..isMouseOver = true).withOpacity(0);
var padding = this.padding ?? EdgeInsets.zero;
var animationMs =
mouseState.isMouseOver ? (animate ? 100 : 0) : (animate ? 200 : 0);
Widget iconWithPadding = Padding(padding: padding, child: icon);
iconWithPadding = AnimatedContainer(
curve: Curves.easeOut,
duration: Duration(milliseconds: animationMs),
color: buttonContext.backgroundColor ?? fadeOutColor,
child: iconWithPadding);
var button = (this.builder != null)
? this.builder!(buttonContext, icon)
: iconWithPadding;
return SizedBox(
width: 48, height: 48, child: button);
},
onPressed: () {
if (this.onPressed != null) this.onPressed!();
},
);
}
}
class MinimizeWindowButton extends WindowButton {
MinimizeWindowButton(
{Key? key,
WindowButtonColors? colors,
VoidCallback? onPressed,
bool? animate})
: super(
key: key,
colors: colors,
animate: animate ?? false,
iconBuilder: (buttonContext) =>
MinimizeIcon(color: buttonContext.iconColor),
onPressed: onPressed ?? () => appWindow.minimize());
}
class MaximizeWindowButton extends WindowButton {
MaximizeWindowButton(
{Key? key,
WindowButtonColors? colors,
VoidCallback? onPressed,
bool? animate})
: super(
key: key,
colors: colors,
animate: animate ?? false,
iconBuilder: (buttonContext) =>
MaximizeIcon(color: buttonContext.iconColor),
onPressed: onPressed ?? () => appWindow.maximizeOrRestore());
}
class RestoreWindowButton extends WindowButton {
RestoreWindowButton(
{Key? key,
WindowButtonColors? colors,
VoidCallback? onPressed,
bool? animate})
: super(
key: key,
colors: colors,
animate: animate ?? false,
iconBuilder: (buttonContext) =>
RestoreIcon(color: buttonContext.iconColor),
onPressed: onPressed ?? () => appWindow.maximizeOrRestore());
}
final _defaultCloseButtonColors = WindowButtonColors(
mouseOver: Color(0xFFD32F2F),
mouseDown: Color(0xFFB71C1C),
iconNormal: Color(0xFF805306),
iconMouseOver: Color(0xFFFFFFFF));
class CloseWindowButton extends WindowButton {
CloseWindowButton(
{Key? key,
WindowButtonColors? colors,
VoidCallback? onPressed,
bool? animate})
: super(
key: key,
colors: colors ?? _defaultCloseButtonColors,
animate: animate ?? false,
iconBuilder: (buttonContext) =>
CloseIcon(color: buttonContext.iconColor),
onPressed: onPressed ?? () => appWindow.close());
}

View File

@@ -0,0 +1,48 @@
import 'package:flutter/widgets.dart';
import '../app_window.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
class _MoveWindow extends StatelessWidget {
_MoveWindow({Key? key, this.child, this.onDoubleTap}) : super(key: key);
final Widget? child;
final VoidCallback? onDoubleTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.translucent,
onPanStart: (details) {
appWindow.startDragging();
},
onDoubleTap: this.onDoubleTap ?? () => appWindow.maximizeOrRestore(),
child: this.child ?? Container());
}
}
class MoveWindow extends StatelessWidget {
final Widget? child;
final VoidCallback? onDoubleTap;
MoveWindow({Key? key, this.child, this.onDoubleTap}) : super(key: key);
@override
Widget build(BuildContext context) {
if (child == null) return _MoveWindow(onDoubleTap: this.onDoubleTap);
return _MoveWindow(
onDoubleTap: this.onDoubleTap,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [Expanded(child: this.child!)]),
);
}
}
class WindowTitleBarBox extends StatelessWidget {
final Widget? child;
WindowTitleBarBox({Key? key, this.child}) : super(key: key);
@override
Widget build(BuildContext context) {
if (kIsWeb) {
return Container();
}
final titlebarHeight = appWindow.titleBarHeight;
return SizedBox(height: titlebarHeight, child: this.child ?? Container());
}
}