Skip to content

Commit

Permalink
Min Max Cursor
Browse files Browse the repository at this point in the history
  • Loading branch information
1cedsoda committed Sep 15, 2022
1 parent 613ce71 commit e160067
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 17 deletions.
5 changes: 4 additions & 1 deletion example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ class MyHomePage extends StatefulWidget {
}

class _MyHomePageState extends State<MyHomePage> {
InteractiveTimelineCubit timelineCubit = InteractiveTimelineCubit();
InteractiveTimelineCubit timelineCubit = InteractiveTimelineCubit(
minCursor: DateTime.now().subtract(Duration(hours: 1)),
maxCursor: DateTime.now().add(Duration(hours: 1)),
);

@override
Widget build(BuildContext context) {
Expand Down
7 changes: 5 additions & 2 deletions lib/src/canvas.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,16 @@ class TimelinePainter extends CustomPainter {
getCandlesInFrame(interval).forEach((x) => paintCandle(canvas, size, x, paint, heightRatio, candleAlignment));
}

List<double> getCandlesInFrame(Duration interval) {
List<double> getCandlesInFrame(Duration interval, [DateTime? min, DateTime? max]) {
// last full minute before cursorLeft
Duration offset = Duration(milliseconds: state.leftCursor.millisecondsSinceEpoch % interval.inMilliseconds);
DateTime startTime = state.leftCursor.subtract(offset);
List<double> minuteCandles = [];
while (startTime.isBefore(state.rightCursor.add(interval))) {
minuteCandles.add(timeToPixel(startTime));
// hie candles out of min max
if (state.insideMinMaxCursor(startTime)) {
minuteCandles.add(timeToPixel(startTime));
}
startTime = startTime.add(interval);
}
return minuteCandles;
Expand Down
57 changes: 44 additions & 13 deletions lib/src/cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,21 @@ class InteractiveTimelineCubit extends Cubit<InteractiveTimelineState> {
Duration timerUpdateInterval; // Milliseconds
double initialZoomLevel; // Seconds per screen width
DateTime? initialTime;
DateTime? minCursor;
DateTime? maxCursor;
InteractiveTimelineCubit({
this.minSecondsPerScreenWidth = 10,
this.maxSecondsPerScreenWidth = 10000,
this.timerUpdateInterval = const Duration(milliseconds: 25),
this.initialZoomLevel = 500,
this.initialTime,
minCursor,
maxCursor,
}) : super(
InteractiveTimelineState.initializeAtTime(
initialTime ?? DateTime.now(),
minCursor,
maxCursor,
),
);

Expand All @@ -40,12 +46,13 @@ class InteractiveTimelineCubit extends Cubit<InteractiveTimelineState> {

void dragHorizontally(DragUpdateDetails update) {
num offsetSeconds = update.primaryDelta! * state.secondsPerPixel;
emit(state.overwrite(
middleCursor: state.middleCursor.subtract(
Duration(milliseconds: (offsetSeconds * 1000).toInt()),
),
isInteracting: true,
));
DateTime newCursor = state.middleCursor.subtract(Duration(milliseconds: (offsetSeconds * 1000).toInt()));
state.insideMinMaxCursor(newCursor)
? emit(state.overwrite(
middleCursor: newCursor,
isInteracting: true,
))
: {};
}

void zoomStart(ScaleStartDetails details) {
Expand Down Expand Up @@ -97,14 +104,22 @@ class InteractiveTimelineCubit extends Cubit<InteractiveTimelineState> {
}
}

// TODO: Using null won't clear the variables
void setMinMax({DateTime? min, DateTime? max}) => emit(state.overwrite(minCursor: min, maxCursor: max));

void _tickTimer(Duration duration) {
if (!state.isInteracting) {
double timerMilliesecondsPerScreenWidth = 10 * 1000; // timer scroll speed
double deltaScreenWidth = duration.inMilliseconds / timerMilliesecondsPerScreenWidth;
Duration deltaCursor = Duration(milliseconds: (deltaScreenWidth * state.secondsPerScreenWidth * 1000).toInt());
emit(state.overwrite(
middleCursor: state.middleCursor.add(deltaCursor),
));
DateTime newCursor = state.middleCursor.add(deltaCursor);
state.insideMinMaxCursor(newCursor)
? emit(
state.overwrite(
middleCursor: newCursor,
),
)
: stopTimer();
}
}

Expand All @@ -128,6 +143,8 @@ class InteractiveTimelineState extends Equatable {
required this.middleCursor,
required this.leftCursor,
required this.rightCursor,
required this.minCursor,
required this.maxCursor,
required this.playTimer,
required this.isPlaying,
required this.isInteracting,
Expand All @@ -141,14 +158,16 @@ class InteractiveTimelineState extends Equatable {
final DateTime middleCursor;
final DateTime leftCursor;
final DateTime rightCursor;
final DateTime? minCursor;
final DateTime? maxCursor;
final Timer? playTimer;
final bool isPlaying;
final bool isInteracting;

@override
List<Object> get props => [width, height, secondsPerPixel, secondsPerScreenWidth, secondsPerScreenWidthBeforeZoom, middleCursor, leftCursor, rightCursor, isPlaying, isInteracting];

static InteractiveTimelineState initializeAtTime(DateTime time) {
static InteractiveTimelineState initializeAtTime(DateTime time, DateTime? minCursor, DateTime? maxCursor) {
return InteractiveTimelineState(
width: 0,
height: 0,
Expand All @@ -158,6 +177,8 @@ class InteractiveTimelineState extends Equatable {
middleCursor: time,
leftCursor: time,
rightCursor: time,
minCursor: minCursor,
maxCursor: maxCursor,
playTimer: null,
isPlaying: false,
isInteracting: false,
Expand All @@ -176,12 +197,20 @@ class InteractiveTimelineState extends Equatable {
);
}

bool insideMinMaxCursor(DateTime time) {
if (minCursor != null) if (time.isBefore(minCursor!)) return false;
if (maxCursor != null) if (time.isAfter(maxCursor!)) return false;
return true;
}

InteractiveTimelineState overwrite({
double? width,
double? height,
double? secondsPerScreenWidth,
double? secondsPerScreenWidthBeforeZoom,
DateTime? middleCursor,
DateTime? minCursor,
DateTime? maxCursor,
Timer? playTimer,
bool? isPlaying,
bool? isInteracting,
Expand All @@ -198,6 +227,8 @@ class InteractiveTimelineState extends Equatable {
middleCursor: middleCursor ?? this.middleCursor,
leftCursor: getLeftCursor(width ?? this.width, newSecondsPerPixel),
rightCursor: getRightCursor(width ?? this.width, newSecondsPerPixel),
minCursor: minCursor ?? this.minCursor,
maxCursor: maxCursor ?? this.maxCursor,
playTimer: playTimer ?? this.playTimer,
isPlaying: isPlaying ?? this.isPlaying,
isInteracting: isInteracting ?? this.isInteracting,
Expand All @@ -206,9 +237,9 @@ class InteractiveTimelineState extends Equatable {
}

class InteractiveTimelineBlocBuilder extends StatelessWidget {
InteractiveTimelineCubit bloc;
Widget Function(BuildContext, InteractiveTimelineState) builder;
InteractiveTimelineBlocBuilder({
final InteractiveTimelineCubit bloc;
final Widget Function(BuildContext, InteractiveTimelineState) builder;
const InteractiveTimelineBlocBuilder({
Key? key,
required this.bloc,
required this.builder,
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: interactive_timeline
description: Draggable and zoomable interactive timeline.
version: 1.0.0-dev.2
version: 1.0.0-dev.3
# homepage: https://www.example.com

environment:
Expand Down

0 comments on commit e160067

Please sign in to comment.