Skip to content

Commit

Permalink
Make resizable box easier to use
Browse files Browse the repository at this point in the history
  • Loading branch information
Pante committed Jul 25, 2023
1 parent f5bf1ce commit cedf846
Show file tree
Hide file tree
Showing 23 changed files with 404 additions and 341 deletions.
6 changes: 3 additions & 3 deletions stevia/example/lib/resizable_box_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,19 @@ class HomeWidget extends StatelessWidget {
ResizableRegion(
initialSize: 200,
sliderSize: 60,
builder: (context, enabled, size, child) => child!,
builder: (context, snapshot, child) => child!,
child: Container(color: Colors.greenAccent),
),
ResizableRegion(
initialSize: 250,
sliderSize: 60,
builder: (context, enabled, size, child) => child!,
builder: (context, snapshot, child) => child!,
child: Container(color: Colors.yellowAccent),
),
ResizableRegion(
initialSize: 150,
sliderSize: 60,
builder: (context, enabled, size, child) => child!,
builder: (context, snapshot, child) => child!,
child: Container(color: Colors.redAccent),
),
],
Expand Down
2 changes: 1 addition & 1 deletion stevia/lib/haptic.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
///
/// test('some test', () async {
/// await UnderTest().foo();
/// expect(calls.length, 1);
/// expect(calls.size, 1);
/// });
///
/// test('nothing', () {
Expand Down
2 changes: 1 addition & 1 deletion stevia/lib/src/haptic/haptic.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import 'package:flutter/services.dart';
///
/// test('some test', () async {
/// await UnderTest().foo();
/// expect(calls.length, 1);
/// expect(calls.size, 1);
/// });
///
/// test('nothing', () {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@ import 'package:flutter/widgets.dart';
import 'package:sugar/sugar.dart';

import 'package:stevia/stevia.dart';
import 'package:stevia/src/widgets/resizable/box/resizable_box_model.dart';
import 'package:stevia/src/widgets/resizable/box/resizable_box_region.dart';
import 'package:stevia/src/widgets/resizable/box/resizable_region.dart';
import 'package:stevia/src/widgets/resizable/box/resizable_region_change_notifier.dart';
import 'package:stevia/src/widgets/resizable/resizable_box_model.dart';
import 'package:stevia/src/widgets/resizable/resizable_box_region.dart';
import 'package:stevia/src/widgets/resizable/resizable_region.dart';
import 'package:stevia/src/widgets/resizable/resizable_region_change_notifier.dart';

/// A box which children can all be resized either horizontally or vertically.
///
/// Each child must be selected by tapping on it before it can be resized.
///
/// ## Contract
/// Each child has a minimum size determined by its slider size multiplied by 2. Setting an initial size smaller than the
/// required minimum size will result in undefined behaviour.
/// Each child has a minimum size determined by its slider size multiplied by 2. Setting an initial size smaller
/// than the required minimum size will result in undefined behaviour.
///
/// A [ResizableBox] should contain at least two children. Passing it less than 2 children will result in undefined behaviour.
///
Expand Down Expand Up @@ -80,15 +80,15 @@ sealed class ResizableBox extends StatefulWidget {
/// ## Contract
/// Throws an [AssertionError] if:
/// * either [height] or [width] is not positive.
/// * [initialIndex] is not in the range `0 <= initialIndex < children.length`.
/// * [initialIndex] is not in the range `0 <= initialIndex < children.size`.
/// * less than two [ResizableRegion]s are given.
/// * the size of all [ResizableRegion]s are not equal to the height, if this box is vertically resizable, or width,
/// if this box is horizontally resizable.
factory ResizableBox({
required double height,
required double width,
required int initialIndex,
required List<ResizableRegion> children,
int initialIndex = 0,
bool horizontal = false,
Key? key,
}) => horizontal ?
Expand Down Expand Up @@ -124,12 +124,16 @@ sealed class _ResizableBoxState<T extends ResizableBox> extends State<T> {

void _update(int selected) {
final regions = <ResizableRegionChangeNotifier>[];
var min = 0.0;
for (final region in widget.children) {
final min = region.sliderSize * 2;
regions.add(ResizableRegionChangeNotifier(min, region.initialSize, _size));
regions.add(ResizableRegionChangeNotifier(
(min: region.sliderSize * 2, max: _size),
min,
min += region.initialSize,
));
}

model = ResizableBoxModel(regions, selected);
model = ResizableBoxModel(regions, _size, selected);
}

double get _size;
Expand All @@ -140,12 +144,12 @@ sealed class _ResizableBoxState<T extends ResizableBox> extends State<T> {
class _HorizontalResizableBox extends ResizableBox {

_HorizontalResizableBox({
required super.height,
required super.width,
required super.initialIndex,
required super.children,
super.key,
}): assert(children.sum((e) => e.initialSize) == width, 'The sum of the initial sizes of all children, ${children.sum((e) => e.initialSize)}, is not equal to the width of the RegionBox, $width.'),
required super.height,
required super.width,
required super.initialIndex,
required super.children,
super.key,
}): assert(children.sum((e) => e.initialSize) == width, 'The sum of the initial sizes of all children, ${children.sum((e) => e.initialSize)}, is not equal to the width of the RegionBox, $width.'),
super._();


Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';

import 'package:stevia/src/widgets/resizable/direction.dart';
import 'package:stevia/stevia.dart';
import 'package:stevia/src/widgets/resizable/box/direction.dart';
import 'package:stevia/src/widgets/resizable/box/resizable_region_change_notifier.dart';
import 'package:stevia/src/widgets/resizable/resizable_region_change_notifier.dart';

/// A resizable box's model.
///
Expand All @@ -13,10 +13,12 @@ import 'package:stevia/src/widgets/resizable/box/resizable_region_change_notifie

/// The [ResizableRegionChangeNotifier] for all regions in this [ResizableBox].
final List<ResizableRegionChangeNotifier> notifiers;
/// The total size of the [ResizableBox].
final double size;
int _selected;

/// Creates a [ResizableBoxModel].
ResizableBoxModel(this.notifiers, this._selected):
ResizableBoxModel(this.notifiers, this.size, this._selected):
assert(2 <= notifiers.length, 'A ResizableBox should have at least 2 ResizableRegions.'),
assert(0 <= _selected && _selected < notifiers.length, 'The selected index should be in 0 <= selected < number of regions, but it is $_selected.');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import 'package:flutter/widgets.dart';
import 'package:meta/meta.dart';

import 'package:stevia/stevia.dart';
import 'package:stevia/src/widgets/resizable/box/resizable_box_model.dart';
import 'package:stevia/src/widgets/resizable/box/resizable_region_change_notifier.dart';
import 'package:stevia/src/widgets/resizable/box/slider.dart';
import 'package:stevia/src/widgets/resizable/resizable_box_model.dart';
import 'package:stevia/src/widgets/resizable/resizable_region_change_notifier.dart';
import 'package:stevia/src/widgets/resizable/slider.dart';

/// A resizable region in a horizontal [ResizableBox].
@internal class HorizontalResizableBoxRegion extends StatelessWidget {
Expand All @@ -28,19 +28,29 @@ import 'package:stevia/src/widgets/resizable/box/slider.dart';
right = HorizontalSlider.right(model: model, index: index, size: region.sliderSize);

@override
Widget build(BuildContext context) => ListenableBuilder(
listenable: notifier,
builder: (context, _) => SizedBox(
width: notifier.current,
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
Haptic.selection();
model.selected = index;
},
Widget build(BuildContext context) => GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
Haptic.selection();
model.selected = index;
},
child: ListenableBuilder(
listenable: notifier,
builder: (context, _) => SizedBox(
width: notifier.size,
child: Stack(
children: [
region.builder(context, model.selected == index, notifier.current, region.child),
region.builder(
context,
RegionSnapshot(
index: index,
enabled: model.selected == index,
total: model.size,
min: notifier.min,
max: notifier.max,
),
region.child,
),
left,
right,
],
Expand Down Expand Up @@ -73,19 +83,29 @@ import 'package:stevia/src/widgets/resizable/box/slider.dart';
bottom = VerticalSlider.bottom(model: model, index: index, size: region.sliderSize);

@override
Widget build(BuildContext context) => ListenableBuilder(
listenable: notifier,
builder: (context, _) => SizedBox(
height: notifier.current,
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
Haptic.selection();
model.selected = index;
},
Widget build(BuildContext context) => GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
Haptic.selection();
model.selected = index;
},
child: ListenableBuilder(
listenable: notifier,
builder: (context, _) => SizedBox(
height: notifier.size,
child: Stack(
children: [
region.builder(context, model.selected == index, notifier.current, region.child),
region.builder(
context,
RegionSnapshot(
index: index,
enabled: model.selected == index,
total: model.size,
min: notifier.min,
max: notifier.max,
),
region.child,
),
top,
bottom,
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,48 @@ import 'package:flutter/widgets.dart';
import 'package:stevia/stevia.dart';

/// The signature of a method for building a [ResizableRegion]'s child.
typedef ResizableRegionBuilder = Widget Function(BuildContext context, bool enabled, double size, Widget? child); // ignore: avoid_positional_boolean_parameters
typedef ResizableRegionBuilder = Widget Function(BuildContext context, RegionSnapshot, Widget? child); // ignore: avoid_positional_boolean_parameters

/// A snapshot of a region.
final class RegionSnapshot {
/// This region's index.
final int index;
/// Whether the region is enabled.
final bool enabled;
/// The total size of the containing [ResizableBox].
final double total;
/// This region's minimum as a cartesian coordinate.
final double min;
/// This region's maximum as a cartesian coordinate.
final double max;

/// Creates a [RegionSnapshot].
RegionSnapshot({required this.index, required this.enabled, required this.total, required this.min, required this.max});

/// The [min] and [max] as percentages of the [total] size.
(double, double) get percentage => (min / total, max / total);

/// This region's size.
double get size => max - min;

@override
bool operator ==(Object other) => identical(this, other) || other is RegionSnapshot && runtimeType == other.runtimeType &&
index == other.index &&
enabled == other.enabled &&
total == other.total &&
min == other.min &&
max == other.max;

@override
int get hashCode => index.hashCode ^ enabled.hashCode ^ total.hashCode ^ min.hashCode ^ max.hashCode;

@override
String toString() => 'RegionSnapshot[index: $index, enabled: $enabled, total: $total, min: $min, max: $max]';
}

/// A [ResizableRegion] is a region in a [ResizableBox].
///
/// Each region has a minimum size determined by `2 * [sliderSize]`.
/// Each region has a minimum size determined by `2 * [sliderLength]`.
final class ResizableRegion {

/// The initial height or width of this region.
Expand All @@ -18,8 +55,8 @@ final class ResizableRegion {
final double initialSize;
/// The sliders' height or width, defaults to `50`.
///
/// A [ResizableRegion] always has 2 sliders. The minimum size of a region is determined by the sum of the sliders' size.
/// A larger [sliderSize] will increase the minimum region size.
/// A [ResizableRegion] always has 2 sliders. The minimum size of a region is determined by the sum of the sliders'
/// size. A larger [sliderSize] will increase the minimum region size.
///
/// ## Contract
/// Providing a negative value will result in undefined behaviour.
Expand Down
Loading

0 comments on commit cedf846

Please sign in to comment.