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

Fix resizable #160

Merged
merged 2 commits into from
Aug 18, 2024
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
16 changes: 11 additions & 5 deletions forui/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
## 0.5.0
## 0.5.0 (Next)

### Additions
* Add `FButton.icon(...)`.

* Add `FFormFieldStyle`.

* Add `FResizable.semanticFormatterCallback`.

### Changes

* Change `FResizable` to resize by `FResizable.resizePercentage` when using a keyboard.

* Change `FTextFieldStyle` to inherit from `FFormFieldStyle`.

* Change `FTextField` to display error under description instead of replacing it.

* **Breaking:** Rename `FDivider.width` to `FDivder.thickness`.

* **Breaking:** Remove `FTextField.error`.

* **Breaking:** Change `FTextField.help` to `FTextField.description`.

* **Breaking:** Change how `FTextFieldStyle` stores various state-dependent styles.

* **Breaking:** Remove `FTextField.error`.

### Fixes

* Fix `FResizable` not rendering properly in an expanded widget when its crossAxisExtent is null.

* Fix `FTextField` not changing error text color when an error occurs.


Expand Down
2 changes: 1 addition & 1 deletion forui/example/macos/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Cocoa
import FlutterMacOS

@NSApplicationMain
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
Expand Down
22 changes: 11 additions & 11 deletions forui/lib/src/widgets/divider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ final class FDivider extends StatelessWidget {
Widget build(BuildContext context) {
final style =
this.style ?? (vertical ? context.theme.dividerStyles.vertical : context.theme.dividerStyles.horizontal);
final (height, width) = vertical ? (null, style.thickness) : (style.thickness, null);
final (height, width) = vertical ? (null, style.width) : (style.width, null);

return Container(
margin: style.padding,
Expand Down Expand Up @@ -122,24 +122,24 @@ final class FDividerStyle with Diagnosticable {
/// The padding surrounding the separating line. Defaults to the appropriate padding in [defaultPadding].
final EdgeInsetsGeometry padding;

/// The thickness of the separating line. Defaults to 1.
/// The width of the separating line. Defaults to 1.
///
/// ## Contract
/// Throws [AssertionError] if:
/// * `thickness` <= 0.0
/// * `thickness` is Nan
final double thickness;
final double width;

/// Creates a [FDividerStyle].
FDividerStyle({required this.color, required this.padding, this.thickness = 1})
: assert(0 < thickness, 'The thickness is $thickness, but it should be in the range "0 < thickness".');
FDividerStyle({required this.color, required this.padding, this.width = 1})
: assert(0 < width, 'The thickness is $width, but it should be in the range "0 < thickness".');

/// Creates a [FDividerStyle] that inherits its properties from [colorScheme], [style], and [padding].
FDividerStyle.inherit({
required FColorScheme colorScheme,
required FStyle style,
required EdgeInsetsGeometry padding,
}) : this(color: colorScheme.secondary, padding: padding, thickness: style.borderWidth);
}) : this(color: colorScheme.secondary, padding: padding, width: style.borderWidth);

/// Returns a copy of this [FDividerStyle] with the given properties replaced.
///
Expand All @@ -156,10 +156,10 @@ final class FDividerStyle with Diagnosticable {
/// print(copy.width); // 2
/// ```
@useResult
FDividerStyle copyWith({Color? color, EdgeInsetsGeometry? padding, double? thickness}) => FDividerStyle(
FDividerStyle copyWith({Color? color, EdgeInsetsGeometry? padding, double? width}) => FDividerStyle(
color: color ?? this.color,
padding: padding ?? this.padding,
thickness: thickness ?? this.thickness,
width: width ?? this.width,
);

@override
Expand All @@ -168,7 +168,7 @@ final class FDividerStyle with Diagnosticable {
properties
..add(DiagnosticsProperty('padding', padding))
..add(ColorProperty('color', color))
..add(DoubleProperty('thickness', thickness));
..add(DoubleProperty('width', width));
}

@override
Expand All @@ -178,8 +178,8 @@ final class FDividerStyle with Diagnosticable {
runtimeType == other.runtimeType &&
color == other.color &&
padding == other.padding &&
thickness == other.thickness;
width == other.width;

@override
int get hashCode => color.hashCode ^ padding.hashCode ^ thickness.hashCode;
int get hashCode => color.hashCode ^ padding.hashCode ^ width.hashCode;
}
128 changes: 66 additions & 62 deletions forui/lib/src/widgets/resizable/divider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,98 +6,109 @@ import 'package:meta/meta.dart';

import 'package:forui/forui.dart';

@internal
class ResizeUpIntent extends Intent {
const ResizeUpIntent();
class _Up extends Intent {
const _Up();
}

@internal
class ResizeDownIntent extends Intent {
const ResizeDownIntent();
class _Down extends Intent {
const _Down();
}

@internal
sealed class Divider extends StatelessWidget {
final FResizableController controller;
final FResizableDividerStyle style;
final FResizableDivider type;
final ({int left, int right}) indexes;
final int left;
final int right;
final double? crossAxisExtent;
final double hitRegionExtent;
final double resizePercentage;
final MouseCursor cursor;
final String Function(FResizableRegionData, FResizableRegionData) semanticFormatterCallback;

Divider({
const Divider({
required this.controller,
required this.style,
required this.type,
required this.indexes,
required this.left,
required this.right,
required this.crossAxisExtent,
required this.hitRegionExtent,
required this.resizePercentage,
required this.cursor,
required this.semanticFormatterCallback,
super.key,
}) : assert(0 <= indexes.left, 'Left should be non-negative, but is ${indexes.left}.'),
assert(indexes.left + 1 == indexes.right, 'Left and right should be next to each other.');

Widget focusableActionDetector({required List<Widget> children}) => MouseRegion(
cursor: cursor,
child: Semantics(
slider: true,
child: Shortcuts(
shortcuts: shortcuts,
child: Actions(
actions: {
ResizeUpIntent: CallbackAction<ResizeUpIntent>(
onInvoke: (intent) => controller.update(indexes.left, indexes.right, -3),
),
ResizeDownIntent: CallbackAction<ResizeDownIntent>(
onInvoke: (intent) => controller.update(indexes.left, indexes.right, 3),
),
},
child: Focus(
child: Stack(
alignment: AlignmentDirectional.center,
children: children,
),
),
}) : assert(0 <= left, 'Left child should be non-negative, but is $left.'),
assert(left + 1 == right, 'Left and right should be next to each other.');

Widget focusableActionDetector({
required Map<ShortcutActivator, Intent> shortcuts,
required List<Widget> children,
}) =>
Semantics(
value: semanticFormatterCallback(controller.regions[left], controller.regions[right]),
child: FocusableActionDetector(
mouseCursor: cursor,
shortcuts: shortcuts,
actions: {
_Up: CallbackAction(
onInvoke: (_) =>
controller.update(left, right, -resizePercentage * (controller.regions[left].extent.total)),
),
_Down: CallbackAction(
onInvoke: (_) =>
controller.update(left, right, resizePercentage * (controller.regions[left].extent.total)),
),
},
child: Stack(
alignment: AlignmentDirectional.center,
children: children,
),
),
);

Map<ShortcutActivator, Intent> get shortcuts;

@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty('controller', controller))
..add(DiagnosticsProperty('style', style))
..add(DiagnosticsProperty('type', type))
..add(DiagnosticsProperty('indexes', indexes))
..add(IntProperty('left', left))
..add(IntProperty('right', right))
..add(DoubleProperty('crossAxisExtent', crossAxisExtent))
..add(DoubleProperty('hitRegionExtent', hitRegionExtent))
..add(DoubleProperty('resizePercentage', resizePercentage))
..add(DiagnosticsProperty('cursor', cursor))
..add(DiagnosticsProperty('shortcuts', shortcuts));
..add(DiagnosticsProperty('semanticFormatterCallback', semanticFormatterCallback));
}
}

@internal
class HorizontalDivider extends Divider {
HorizontalDivider({
const HorizontalDivider({
required super.controller,
required super.style,
required super.type,
required super.indexes,
required super.left,
required super.right,
required super.crossAxisExtent,
required super.hitRegionExtent,
required super.resizePercentage,
required super.cursor,
required super.semanticFormatterCallback,
super.key,
});

@override
Widget build(BuildContext context) => Positioned(
left: controller.regions[indexes.left].offset.max - (hitRegionExtent / 2),
left: controller.regions[left].offset.max - (hitRegionExtent / 2),
child: focusableActionDetector(
shortcuts: const {
SingleActivator(LogicalKeyboardKey.arrowLeft): _Up(),
SingleActivator(LogicalKeyboardKey.arrowRight): _Down(),
},
children: [
if (type == FResizableDivider.divider || type == FResizableDivider.dividerWithThumb)
ColoredBox(
Expand All @@ -116,46 +127,46 @@ class HorizontalDivider extends Divider {
height: crossAxisExtent,
width: hitRegionExtent,
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onHorizontalDragUpdate: (details) {
if (details.delta.dx == 0.0) {
return;
}

controller.update(indexes.left, indexes.right, details.delta.dx);
controller.update(left, right, details.delta.dx);
// TODO: haptic feedback
},
onHorizontalDragEnd: (details) => controller.end(indexes.left, indexes.right),
onHorizontalDragEnd: (details) => controller.end(left, right),
),
),
],
),
);

@override
Map<ShortcutActivator, Intent> get shortcuts => const {
SingleActivator(LogicalKeyboardKey.arrowLeft): ResizeUpIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight): ResizeDownIntent(),
};
}

@internal
class VerticalDivider extends Divider {
VerticalDivider({
const VerticalDivider({
required super.controller,
required super.style,
required super.type,
required super.indexes,
required super.left,
required super.right,
required super.crossAxisExtent,
required super.hitRegionExtent,
required super.resizePercentage,
required super.cursor,
required super.semanticFormatterCallback,
super.key,
});

@override
Widget build(BuildContext context) => Positioned(
top: controller.regions[indexes.left].offset.max - (hitRegionExtent / 2),
top: controller.regions[left].offset.max - (hitRegionExtent / 2),
child: focusableActionDetector(
shortcuts: const {
SingleActivator(LogicalKeyboardKey.arrowUp): _Up(),
SingleActivator(LogicalKeyboardKey.arrowDown): _Down(),
},
children: [
if (type == FResizableDivider.divider || type == FResizableDivider.dividerWithThumb)
ColoredBox(
Expand All @@ -174,27 +185,20 @@ class VerticalDivider extends Divider {
height: hitRegionExtent,
width: crossAxisExtent,
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onVerticalDragUpdate: (details) {
if (details.delta.dy == 0.0) {
return;
}

controller.update(indexes.left, indexes.right, details.delta.dy);
controller.update(left, right, details.delta.dy);
// TODO: haptic feedback
},
onVerticalDragEnd: (details) => controller.end(indexes.left, indexes.right),
onVerticalDragEnd: (details) => controller.end(left, right),
),
),
],
),
);

@override
Map<ShortcutActivator, Intent> get shortcuts => const {
SingleActivator(LogicalKeyboardKey.arrowUp): ResizeUpIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown): ResizeDownIntent(),
};
}

class _Thumb extends StatelessWidget {
Expand Down Expand Up @@ -226,7 +230,7 @@ class _Thumb extends StatelessWidget {
}
}

/// The type of dividers between [FResizableRegion]s.
/// The appearance of dividers between [FResizableRegion]s.
enum FResizableDivider {
/// No divider.
none,
Expand Down
Loading