From eda69191b512c4249fa64b983cf0c32dd17c64a1 Mon Sep 17 00:00:00 2001 From: Divyanshu Bhargava Date: Sat, 3 May 2025 23:46:11 +0530 Subject: [PATCH 1/2] feat: add ClipRRect widget support --- packages/stac/lib/src/framework/stac.dart | 3 +- .../stac_clip_rrect/stac_clip_rrect.dart | 21 ++ .../stac_clip_rrect.freezed.dart | 240 ++++++++++++++++++ .../stac_clip_rrect/stac_clip_rrect.g.dart | 31 +++ .../stac_clip_rrect_parser.dart | 26 ++ .../stac/lib/src/parsers/widgets/widgets.dart | 1 + packages/stac/lib/src/utils/widget_type.dart | 1 + 7 files changed, 322 insertions(+), 1 deletion(-) create mode 100644 packages/stac/lib/src/parsers/widgets/stac_clip_rrect/stac_clip_rrect.dart create mode 100644 packages/stac/lib/src/parsers/widgets/stac_clip_rrect/stac_clip_rrect.freezed.dart create mode 100644 packages/stac/lib/src/parsers/widgets/stac_clip_rrect/stac_clip_rrect.g.dart create mode 100644 packages/stac/lib/src/parsers/widgets/stac_clip_rrect/stac_clip_rrect_parser.dart diff --git a/packages/stac/lib/src/framework/stac.dart b/packages/stac/lib/src/framework/stac.dart index 9246320c..14119e09 100644 --- a/packages/stac/lib/src/framework/stac.dart +++ b/packages/stac/lib/src/framework/stac.dart @@ -96,7 +96,8 @@ class Stac { const StacFittedBoxParser(), const StacLimitedBoxParser(), const StacDynamicViewParser(), - const StacDropdownMenuParser() + const StacDropdownMenuParser(), + const StacClipRRectParser(), ]; static final _actionParsers = [ diff --git a/packages/stac/lib/src/parsers/widgets/stac_clip_rrect/stac_clip_rrect.dart b/packages/stac/lib/src/parsers/widgets/stac_clip_rrect/stac_clip_rrect.dart new file mode 100644 index 00000000..8126b3a9 --- /dev/null +++ b/packages/stac/lib/src/parsers/widgets/stac_clip_rrect/stac_clip_rrect.dart @@ -0,0 +1,21 @@ +import 'dart:ui'; + +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:stac/src/parsers/parsers.dart'; + +export 'stac_clip_rrect_parser.dart'; + +part 'stac_clip_rrect.freezed.dart'; +part 'stac_clip_rrect.g.dart'; + +@freezed +abstract class StacClipRRect with _$StacClipRRect { + const factory StacClipRRect({ + @Default(StacBorderRadius()) StacBorderRadius borderRadius, + @Default(Clip.antiAlias) Clip clipBehavior, + Map? child, + }) = _StacClipRRect; + + factory StacClipRRect.fromJson(Map json) => + _$StacClipRRectFromJson(json); +} diff --git a/packages/stac/lib/src/parsers/widgets/stac_clip_rrect/stac_clip_rrect.freezed.dart b/packages/stac/lib/src/parsers/widgets/stac_clip_rrect/stac_clip_rrect.freezed.dart new file mode 100644 index 00000000..f791c2d0 --- /dev/null +++ b/packages/stac/lib/src/parsers/widgets/stac_clip_rrect/stac_clip_rrect.freezed.dart @@ -0,0 +1,240 @@ +// dart format width=80 +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'stac_clip_rrect.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$StacClipRRect { + StacBorderRadius get borderRadius; + Clip get clipBehavior; + Map? get child; + + /// Create a copy of StacClipRRect + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') + $StacClipRRectCopyWith get copyWith => + _$StacClipRRectCopyWithImpl( + this as StacClipRRect, _$identity); + + /// Serializes this StacClipRRect to a JSON map. + Map toJson(); + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is StacClipRRect && + (identical(other.borderRadius, borderRadius) || + other.borderRadius == borderRadius) && + (identical(other.clipBehavior, clipBehavior) || + other.clipBehavior == clipBehavior) && + const DeepCollectionEquality().equals(other.child, child)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, borderRadius, clipBehavior, + const DeepCollectionEquality().hash(child)); + + @override + String toString() { + return 'StacClipRRect(borderRadius: $borderRadius, clipBehavior: $clipBehavior, child: $child)'; + } +} + +/// @nodoc +abstract mixin class $StacClipRRectCopyWith<$Res> { + factory $StacClipRRectCopyWith( + StacClipRRect value, $Res Function(StacClipRRect) _then) = + _$StacClipRRectCopyWithImpl; + @useResult + $Res call( + {StacBorderRadius borderRadius, + Clip clipBehavior, + Map? child}); + + $StacBorderRadiusCopyWith<$Res> get borderRadius; +} + +/// @nodoc +class _$StacClipRRectCopyWithImpl<$Res> + implements $StacClipRRectCopyWith<$Res> { + _$StacClipRRectCopyWithImpl(this._self, this._then); + + final StacClipRRect _self; + final $Res Function(StacClipRRect) _then; + + /// Create a copy of StacClipRRect + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? borderRadius = null, + Object? clipBehavior = null, + Object? child = freezed, + }) { + return _then(_self.copyWith( + borderRadius: null == borderRadius + ? _self.borderRadius + : borderRadius // ignore: cast_nullable_to_non_nullable + as StacBorderRadius, + clipBehavior: null == clipBehavior + ? _self.clipBehavior + : clipBehavior // ignore: cast_nullable_to_non_nullable + as Clip, + child: freezed == child + ? _self.child + : child // ignore: cast_nullable_to_non_nullable + as Map?, + )); + } + + /// Create a copy of StacClipRRect + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $StacBorderRadiusCopyWith<$Res> get borderRadius { + return $StacBorderRadiusCopyWith<$Res>(_self.borderRadius, (value) { + return _then(_self.copyWith(borderRadius: value)); + }); + } +} + +/// @nodoc +@JsonSerializable() +class _StacClipRRect implements StacClipRRect { + const _StacClipRRect( + {this.borderRadius = const StacBorderRadius(), + this.clipBehavior = Clip.antiAlias, + final Map? child}) + : _child = child; + factory _StacClipRRect.fromJson(Map json) => + _$StacClipRRectFromJson(json); + + @override + @JsonKey() + final StacBorderRadius borderRadius; + @override + @JsonKey() + final Clip clipBehavior; + final Map? _child; + @override + Map? get child { + final value = _child; + if (value == null) return null; + if (_child is EqualUnmodifiableMapView) return _child; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + + /// Create a copy of StacClipRRect + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + @pragma('vm:prefer-inline') + _$StacClipRRectCopyWith<_StacClipRRect> get copyWith => + __$StacClipRRectCopyWithImpl<_StacClipRRect>(this, _$identity); + + @override + Map toJson() { + return _$StacClipRRectToJson( + this, + ); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _StacClipRRect && + (identical(other.borderRadius, borderRadius) || + other.borderRadius == borderRadius) && + (identical(other.clipBehavior, clipBehavior) || + other.clipBehavior == clipBehavior) && + const DeepCollectionEquality().equals(other._child, _child)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, borderRadius, clipBehavior, + const DeepCollectionEquality().hash(_child)); + + @override + String toString() { + return 'StacClipRRect(borderRadius: $borderRadius, clipBehavior: $clipBehavior, child: $child)'; + } +} + +/// @nodoc +abstract mixin class _$StacClipRRectCopyWith<$Res> + implements $StacClipRRectCopyWith<$Res> { + factory _$StacClipRRectCopyWith( + _StacClipRRect value, $Res Function(_StacClipRRect) _then) = + __$StacClipRRectCopyWithImpl; + @override + @useResult + $Res call( + {StacBorderRadius borderRadius, + Clip clipBehavior, + Map? child}); + + @override + $StacBorderRadiusCopyWith<$Res> get borderRadius; +} + +/// @nodoc +class __$StacClipRRectCopyWithImpl<$Res> + implements _$StacClipRRectCopyWith<$Res> { + __$StacClipRRectCopyWithImpl(this._self, this._then); + + final _StacClipRRect _self; + final $Res Function(_StacClipRRect) _then; + + /// Create a copy of StacClipRRect + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $Res call({ + Object? borderRadius = null, + Object? clipBehavior = null, + Object? child = freezed, + }) { + return _then(_StacClipRRect( + borderRadius: null == borderRadius + ? _self.borderRadius + : borderRadius // ignore: cast_nullable_to_non_nullable + as StacBorderRadius, + clipBehavior: null == clipBehavior + ? _self.clipBehavior + : clipBehavior // ignore: cast_nullable_to_non_nullable + as Clip, + child: freezed == child + ? _self._child + : child // ignore: cast_nullable_to_non_nullable + as Map?, + )); + } + + /// Create a copy of StacClipRRect + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $StacBorderRadiusCopyWith<$Res> get borderRadius { + return $StacBorderRadiusCopyWith<$Res>(_self.borderRadius, (value) { + return _then(_self.copyWith(borderRadius: value)); + }); + } +} + +// dart format on diff --git a/packages/stac/lib/src/parsers/widgets/stac_clip_rrect/stac_clip_rrect.g.dart b/packages/stac/lib/src/parsers/widgets/stac_clip_rrect/stac_clip_rrect.g.dart new file mode 100644 index 00000000..2bddb624 --- /dev/null +++ b/packages/stac/lib/src/parsers/widgets/stac_clip_rrect/stac_clip_rrect.g.dart @@ -0,0 +1,31 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'stac_clip_rrect.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_StacClipRRect _$StacClipRRectFromJson(Map json) => + _StacClipRRect( + borderRadius: json['borderRadius'] == null + ? const StacBorderRadius() + : StacBorderRadius.fromJson(json['borderRadius']), + clipBehavior: $enumDecodeNullable(_$ClipEnumMap, json['clipBehavior']) ?? + Clip.antiAlias, + child: json['child'] as Map?, + ); + +Map _$StacClipRRectToJson(_StacClipRRect instance) => + { + 'borderRadius': instance.borderRadius, + 'clipBehavior': _$ClipEnumMap[instance.clipBehavior]!, + 'child': instance.child, + }; + +const _$ClipEnumMap = { + Clip.none: 'none', + Clip.hardEdge: 'hardEdge', + Clip.antiAlias: 'antiAlias', + Clip.antiAliasWithSaveLayer: 'antiAliasWithSaveLayer', +}; diff --git a/packages/stac/lib/src/parsers/widgets/stac_clip_rrect/stac_clip_rrect_parser.dart b/packages/stac/lib/src/parsers/widgets/stac_clip_rrect/stac_clip_rrect_parser.dart new file mode 100644 index 00000000..76489364 --- /dev/null +++ b/packages/stac/lib/src/parsers/widgets/stac_clip_rrect/stac_clip_rrect_parser.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:stac/src/framework/framework.dart'; +import 'package:stac/src/parsers/widgets/stac_border_radius/stac_border_radius.dart'; +import 'package:stac/src/parsers/widgets/stac_clip_rrect/stac_clip_rrect.dart'; +import 'package:stac/src/utils/widget_type.dart'; +import 'package:stac_framework/stac_framework.dart'; + +class StacClipRRectParser extends StacParser { + const StacClipRRectParser(); + + @override + String get type => WidgetType.clipRRect.name; + + @override + StacClipRRect getModel(Map json) => + StacClipRRect.fromJson(json); + + @override + Widget parse(BuildContext context, StacClipRRect model) { + return ClipRRect( + borderRadius: model.borderRadius.parse, + clipBehavior: model.clipBehavior, + child: Stac.fromJson(model.child, context), + ); + } +} diff --git a/packages/stac/lib/src/parsers/widgets/widgets.dart b/packages/stac/lib/src/parsers/widgets/widgets.dart index 498ee45b..f7b50a5d 100644 --- a/packages/stac/lib/src/parsers/widgets/widgets.dart +++ b/packages/stac/lib/src/parsers/widgets/widgets.dart @@ -23,6 +23,7 @@ export 'package:stac/src/parsers/widgets/stac_check_box/stac_check_box.dart'; export 'package:stac/src/parsers/widgets/stac_chip/stac_chip.dart'; export 'package:stac/src/parsers/widgets/stac_circle_avatar/stac_circle_avatar.dart'; export 'package:stac/src/parsers/widgets/stac_circular_progress_indicator/stac_circular_progress_indicator.dart'; +export 'package:stac/src/parsers/widgets/stac_clip_rrect/stac_clip_rrect.dart'; export 'package:stac/src/parsers/widgets/stac_colored_box/stac_colored_box.dart'; export 'package:stac/src/parsers/widgets/stac_column/stac_column.dart'; export 'package:stac/src/parsers/widgets/stac_container/stac_container.dart'; diff --git a/packages/stac/lib/src/utils/widget_type.dart b/packages/stac/lib/src/utils/widget_type.dart index 1f2fef18..47205fc3 100644 --- a/packages/stac/lib/src/utils/widget_type.dart +++ b/packages/stac/lib/src/utils/widget_type.dart @@ -11,6 +11,7 @@ enum WidgetType { center, checkBox, chip, + clipRRect, circleAvatar, circularProgressIndicator, coloredBox, From f2f2a88a6b427d9228c72a46fb80d247575e6552 Mon Sep 17 00:00:00 2001 From: Divyanshu Bhargava Date: Sat, 3 May 2025 23:58:04 +0530 Subject: [PATCH 2/2] docs: add ClipRRect widget documentation with examples and properties --- website/docs/widgets/clip_rrect.md | 149 +++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 website/docs/widgets/clip_rrect.md diff --git a/website/docs/widgets/clip_rrect.md b/website/docs/widgets/clip_rrect.md new file mode 100644 index 00000000..b34c5975 --- /dev/null +++ b/website/docs/widgets/clip_rrect.md @@ -0,0 +1,149 @@ +# ClipRRect + +The `ClipRRect` widget in Stac allows you to clip its child using rounded rectangles. This is useful when you want to create UI elements with rounded corners. + +## Usage + +```json +{ + "type": "clipRRect", + "borderRadius": 8.0, + "clipBehavior": "antiAlias", + "child": { + "type": "container", + "color": "#FF0000", + "height": 100, + "width": 100 + } +} +``` + +## Properties + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `borderRadius` | `StacBorderRadius` | `StacBorderRadius()` (0 for all corners) | The border radius of the rounded corners. Can be specified as a single value, a list of 4 values, or an object with individual corner values. | +| `clipBehavior` | `Clip` | `antiAlias` | The clipping behavior when content extends beyond the rounded rectangle. | +| `child` | `Widget` | Required | The widget to clip with rounded corners. | + +## BorderRadius Format Options + +The `borderRadius` property can be specified in multiple formats: + +### Single Value (applies to all corners) + +```json +{ + "type": "clipRRect", + "borderRadius": 8.0, + "child": { ... } +} +``` + +### List Format (topLeft, topRight, bottomLeft, bottomRight) + +```json +{ + "type": "clipRRect", + "borderRadius": [8.0, 16.0, 8.0, 16.0], + "child": { ... } +} +``` + +### Object Format (specify each corner individually) + +```json +{ + "type": "clipRRect", + "borderRadius": { + "topLeft": 8.0, + "topRight": 16.0, + "bottomLeft": 8.0, + "bottomRight": 16.0 + }, + "child": { ... } +} +``` + +## Clip Behavior Options + +The `clipBehavior` property accepts the following values: + +- `"antiAlias"` (default): Clip using anti-aliasing for smoother edges +- `"hardEdge"`: Clip without anti-aliasing for sharper edges +- `"antiAliasWithSaveLayer"`: Anti-aliased clipping with an offscreen buffer (higher quality but slower) +- `"none"`: No clipping (not recommended for ClipRRect) + +## Examples + +### Basic Rounded Container + +```json +{ + "type": "clipRRect", + "borderRadius": 12.0, + "child": { + "type": "container", + "color": "#2196F3", + "height": 100, + "width": 200 + } +} +``` + +### Rounded Image + +```json +{ + "type": "clipRRect", + "borderRadius": 8.0, + "child": { + "type": "image", + "src": "https://example.com/image.jpg", + "width": 200, + "height": 200, + "fit": "cover" + } +} +``` + +### Card with Different Corner Radii + +```json +{ + "type": "clipRRect", + "borderRadius": { + "topLeft": 20.0, + "topRight": 20.0, + "bottomLeft": 0.0, + "bottomRight": 0.0 + }, + "child": { + "type": "container", + "color": "#FFFFFF", + "padding": 16.0, + "child": { + "type": "column", + "children": [ + { + "type": "text", + "text": "Card Title", + "style": { + "fontSize": 18.0, + "fontWeight": "bold" + } + }, + { + "type": "text", + "text": "Card content goes here" + } + ] + } + } +} +``` + +## Related Widgets + +- [Container](container.md) - Often used as a child of ClipRRect +- [Card](card.md) - A pre-styled container with elevation and rounded corners \ No newline at end of file